diff --git a/.github/workflows/pull-request-merge-check.yml b/.github/workflows/pull-request-merge-check.yml index e5e7b568..14021f90 100644 --- a/.github/workflows/pull-request-merge-check.yml +++ b/.github/workflows/pull-request-merge-check.yml @@ -122,70 +122,6 @@ jobs: path: build/test-results key: unit-test-report-${{ github.sha }} - integration-test: - name: Integration Test - needs: [ setup ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Gradle Wrapper Cache - uses: actions/cache@v4.0.2 - with: - path: ~/.gradle/wrapper - key: gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} - - - name: Dependencies Cache - uses: actions/cache@v4.0.2 - with: - path: | - ~/.gradle/cache/jars-* - ~/.gradle/caches/transforms-* - ~/.gradle/caches/modules-* - key: gradle-dependencies-${{ hashFiles('**/*.gradle.kts') }} - restore-keys: gradle-dependencies- - - - name: Cache - uses: actions/cache@v4.0.2 - with: - path: | - ~/.gradle/caches/build-cache-* - ~/.gradle/caches/[0-9]*.* - .gradle - key: ${{ runner.os }}-gradle-build-${{ github.workflow }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-gradle-build-${{ github.workflow }}- - - - name: Build Cache - uses: actions/cache@v4.0.2 - with: - path: | - build - key: gradle-build-${{ hashFiles('**/*.gradle.kts') }}-${{ hashFiles('src') }}-${{ github.sha }} - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: MongoDB in GitHub Actions - uses: supercharge/mongodb-github-action@1.11.0 - with: - mongodb-version: latest - - - name: Unit Test - uses: gradle/gradle-build-action@v3.4.1 - with: - arguments: :hideout-core:integrationTest - - - name: Save Test Report - if: always() - uses: actions/cache/save@v4 - with: - path: build/test-results - key: integration-test-report-${{ github.sha }} - coverage: name: Coverage needs: [ setup ] @@ -234,9 +170,9 @@ jobs: distribution: 'temurin' - name: Run Kover - uses: gradle/gradle-build-action@v3.4.1 + uses: gradle/gradle-build-action@v3.3.2 with: - arguments: :hideout-core:koverXmlReport -x :hideout-core:integrationTest -x :hideout-core:e2eTest --rerun-tasks + arguments: :hideout-core:koverXmlReport --rerun-tasks - name: Add coverage report to PR if: always() @@ -255,7 +191,7 @@ jobs: report-tests: name: Report Tests if: success() || failure() - needs: [ unit-test,integration-test,e2e-test ] + needs: [ unit-test ] runs-on: ubuntu-latest steps: - name: Restore Test Report @@ -264,18 +200,6 @@ jobs: path: build/test-results key: unit-test-report-${{ github.sha }} - - name: Restore Test Report - uses: actions/cache/restore@v4 - with: - path: build/test-results - key: integration-test-report-${{ github.sha }} - - - name: Restore Test Report - uses: actions/cache/restore@v4 - with: - path: build/test-results - key: e2e-test-report-${{ github.sha }} - - name: JUnit Test Report uses: mikepenz/action-junit-report@v4 with: @@ -337,76 +261,4 @@ jobs: if: ${{ always() }} uses: reviewdog/action-suggester@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - e2e-test: - name: E2E Test - needs: [ setup ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Gradle Wrapper Cache - uses: actions/cache@v4.0.2 - with: - path: ~/.gradle/wrapper - key: gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} - - - name: Dependencies Cache - uses: actions/cache@v4.0.2 - with: - path: | - ~/.gradle/cache/jars-* - ~/.gradle/caches/transforms-* - ~/.gradle/caches/modules-* - key: gradle-dependencies-${{ hashFiles('**/*.gradle.kts') }} - restore-keys: gradle-dependencies- - - - name: Cache - uses: actions/cache@v4.0.2 - with: - path: | - ~/.gradle/caches/build-cache-* - ~/.gradle/caches/[0-9]*.* - .gradle - key: ${{ runner.os }}-gradle-build-${{ github.workflow }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-gradle-build-${{ github.workflow }}- - - - name: Build Cache - uses: actions/cache@v4.0.2 - with: - path: | - build - key: gradle-build-${{ hashFiles('**/*.gradle.kts') }}-${{ hashFiles('src') }}-${{ github.sha }} - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: MongoDB in GitHub Actions - uses: supercharge/mongodb-github-action@1.11.0 - with: - mongodb-version: latest - - - name: setup-chrome - id: setup-chrome - uses: browser-actions/setup-chrome@v1.7.1 - - - name: Add Path - run: echo ${{ steps.setup-chrome.outputs.chrome-path }} >> $GITHUB_PATH - - - name: E2E Test - uses: gradle/gradle-build-action@v3.4.1 - with: - arguments: :hideout-core:e2eTest - - - - name: Save Test Report - if: always() - uses: actions/cache/save@v4 - with: - path: build/test-results - key: e2e-test-report-${{ github.sha }} + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 215512d0..39e4144d 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ out/ *.log /hideout-core/files/ +/hideout-core/.kotlin/sessions/ +/hideout-mastodon/.kotlin/sessions/ diff --git a/build.gradle.kts b/build.gradle.kts index c0e7f565..3ad2888b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,13 +16,54 @@ plugins { alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.spring.boot) + alias(libs.plugins.kotlin.spring) +} + +apply { + plugin("io.spring.dependency-management") +} + +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") + } + } +} +configurations { + all { + exclude("org.springframework.boot", "spring-boot-starter-logging") + exclude("ch.qos.logback", "logback-classic") + } } dependencies { implementation("dev.usbharu:hideout-core:0.0.1") - implementation("dev.usbharu:hideout-worker:0.0.1") + implementation("dev.usbharu:hideout-mastodon:1.0-SNAPSHOT") } tasks.register("run") { dependsOn(gradle.includedBuild("hideout-core").task(":run")) +} + +springBoot { + mainClass = "dev.usbharu.hideout.SpringApplicationKt" } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 89909840..483b2ee6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,6 @@ org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC \ No newline at end of file +org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=warn \ No newline at end of file diff --git a/hideout-activitypub/build.gradle.kts b/hideout-activitypub/build.gradle.kts new file mode 100644 index 00000000..45be2756 --- /dev/null +++ b/hideout-activitypub/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") version "1.9.23" +} + +group = "dev.usbharu" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/hideout-activitypub/gradle/wrapper/gradle-wrapper.jar b/hideout-activitypub/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/hideout-activitypub/gradle/wrapper/gradle-wrapper.jar differ diff --git a/hideout-worker/gradle/wrapper/gradle-wrapper.properties b/hideout-activitypub/gradle/wrapper/gradle-wrapper.properties similarity index 74% rename from hideout-worker/gradle/wrapper/gradle-wrapper.properties rename to hideout-activitypub/gradle/wrapper/gradle-wrapper.properties index a4413138..6729f90e 100644 --- a/hideout-worker/gradle/wrapper/gradle-wrapper.properties +++ b/hideout-activitypub/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ +#Sat Jun 01 11:47:11 JST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hideout-worker/gradlew b/hideout-activitypub/gradlew similarity index 84% rename from hideout-worker/gradlew rename to hideout-activitypub/gradlew index b740cf13..1b6c7873 100644 --- a/hideout-worker/gradlew +++ b/hideout-activitypub/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,11 +80,13 @@ do esac done -# This is normally unused -# shellcheck disable=SC2034 +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -131,29 +133,22 @@ location of your Java installation." fi else JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then done fi - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -214,12 +205,6 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/hideout-worker/gradlew.bat b/hideout-activitypub/gradlew.bat similarity index 80% rename from hideout-worker/gradlew.bat rename to hideout-activitypub/gradlew.bat index 7101f8e4..107acd32 100644 --- a/hideout-worker/gradlew.bat +++ b/hideout-activitypub/gradlew.bat @@ -1,92 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hideout-activitypub/settings.gradle.kts b/hideout-activitypub/settings.gradle.kts new file mode 100644 index 00000000..1fd1691d --- /dev/null +++ b/hideout-activitypub/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "hideout-activitypub" + diff --git a/hideout-activitypub/src/main/kotlin/Main.kt b/hideout-activitypub/src/main/kotlin/Main.kt new file mode 100644 index 00000000..27f6ee1a --- /dev/null +++ b/hideout-activitypub/src/main/kotlin/Main.kt @@ -0,0 +1,5 @@ +package dev.usbharu + +fun main() { + println("Hello World!") +} \ No newline at end of file diff --git a/hideout-core/build.gradle.kts b/hideout-core/build.gradle.kts index ec38328f..fe0e1d5e 100644 --- a/hideout-core/build.gradle.kts +++ b/hideout-core/build.gradle.kts @@ -3,15 +3,13 @@ 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 org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.openapitools.generator.gradle.plugin.tasks.GenerateTask +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.openapi.generator) alias(libs.plugins.kover) alias(libs.plugins.license.report) @@ -24,58 +22,6 @@ apply { group = "dev.usbharu" version = "0.0.1" -sourceSets { - create("intTest") { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - } - create("e2eTest") { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - } -} - -val intTestImplementation by configurations.getting { - extendsFrom(configurations.implementation.get()) -} -val intTestRuntimeOnly by configurations.getting { - extendsFrom(configurations.runtimeOnly.get()) -} - -val e2eTestImplementation by configurations.getting { - extendsFrom(configurations.implementation.get()) -} - -val e2eTestRuntimeOnly by configurations.getting { - extendsFrom(configurations.runtimeOnly.get()) -} - -val integrationTest = task("integrationTest") { - description = "Runs integration tests." - group = "verification" - - testClassesDirs = sourceSets["intTest"].output.classesDirs - classpath = sourceSets["intTest"].runtimeClasspath - shouldRunAfter("test") - - useJUnitPlatform() -} - -val e2eTest = task("e2eTest") { - description = "Runs e2e tests." - group = "verification" - - testClassesDirs = sourceSets["e2eTest"].output.classesDirs - classpath = sourceSets["e2eTest"].runtimeClasspath - shouldRunAfter("test") - - useJUnitPlatform() -} - -tasks.check { - dependsOn(integrationTest) - dependsOn(e2eTest) -} tasks.withType { useJUnitPlatform() @@ -84,44 +30,20 @@ tasks.withType { "--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() } } - -tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict" +kotlin { + jvmToolchain(21) + compilerOptions { + freeCompilerArgs.add("-Xjsr305=strict") + jvmTarget = JvmTarget.JVM_21 } - dependsOn("openApiGenerateMastodonCompatibleApi") - mustRunAfter("openApiGenerateMastodonCompatibleApi") } -tasks.clean { - delete += listOf("$rootDir/src/main/resources/static") -} - -tasks.create("openApiGenerateMastodonCompatibleApi", GenerateTask::class) { - generatorName.set("kotlin-spring") - inputSpec.set("$rootDir/src/main/resources/openapi/mastodon.yaml") - outputDir.set("$buildDir/generated/sources/mastodon") - apiPackage.set("dev.usbharu.hideout.controller.mastodon.generated") - modelPackage.set("dev.usbharu.hideout.domain.mastodon.model.generated") - configOptions.put("interfaceOnly", "true") - configOptions.put("useSpringBoot3", "true") - configOptions.put("reactive", "true") - additionalProperties.put("useTags", "true") - - importMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") - typeMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") - schemaMappings.put( - "StatusesRequest", - "dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest" - ) - templateDir.set("$rootDir/templates") -} - repositories { mavenCentral() maven { @@ -147,21 +69,6 @@ repositories { } } -kotlin { - target { - compilations.all { - kotlinOptions.jvmTarget = JavaVersion.VERSION_21.toString() - } - } -} - -sourceSets.main { - kotlin.srcDirs( - "$buildDir/generated/ksp/main", - "$buildDir/generated/sources/openapi/src/main/kotlin", - "$buildDir/generated/sources/mastodon/src/main/kotlin" - ) -} val os = org.gradle.nativeplatform.platform.internal .DefaultNativePlatform.getCurrentOperatingSystem() @@ -187,6 +94,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-log4j2") implementation("org.springframework.boot:spring-boot-starter-validation") + implementation(libs.blurhash) implementation(libs.aws.s3) implementation(libs.jsoup) @@ -195,6 +103,7 @@ dependencies { implementation(libs.imageio.webp) implementation(libs.thumbnailator) implementation(libs.flyway.core) + runtimeOnly(libs.flyway.postgresql) implementation("dev.usbharu:owl-common-serialize-jackson:0.0.1") @@ -221,30 +130,15 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") - implementation(libs.kotlin.junit) - implementation(libs.coroutines.test) - + testImplementation(libs.kotlin.junit) + testImplementation(libs.coroutines.test) testImplementation(libs.ktor.client.mock) testImplementation(libs.h2db) - testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") testImplementation("org.mockito:mockito-inline:5.2.0") testImplementation("nl.jqno.equalsverifier:equalsverifier:3.16.1") testImplementation("com.jparams:to-string-verifier:1.4.8") - intTestImplementation("org.springframework.boot:spring-boot-starter-test") - intTestImplementation("org.springframework.security:spring-security-test") - intTestImplementation(libs.kotlin.junit) - intTestImplementation(libs.coroutines.test) - intTestImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") - intTestImplementation(libs.h2db) - - e2eTestImplementation("org.springframework.boot:spring-boot-starter-test") - e2eTestImplementation("org.springframework.security:spring-security-test") - e2eTestImplementation("org.springframework.boot:spring-boot-starter-webflux") - e2eTestImplementation("com.intuit.karate:karate-junit5:1.4.1") - e2eTestImplementation(libs.h2db) - } detekt { @@ -332,7 +226,9 @@ kover { } springBoot { - buildInfo() + buildInfo { + + } } licenseReport { diff --git a/hideout-core/gradle.properties b/hideout-core/gradle.properties index 29566cd4..f3b8b6f9 100644 --- a/hideout-core/gradle.properties +++ b/hideout-core/gradle.properties @@ -18,3 +18,5 @@ org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=warn \ No newline at end of file diff --git a/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt b/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt deleted file mode 100644 index 8d73d7c6..00000000 --- a/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import java.net.MalformedURLException -import java.net.URL - -object AssertionUtil { - - @JvmStatic - fun assertUserExist(username: String, domain: String): Boolean { - val s = try { - val url = URL(domain) - url.host + ":" + url.port.toString().takeIf { it != "-1" }.orEmpty() - } catch (e: MalformedURLException) { - domain - } - - val selectAll = Actors.selectAll() - println(selectAll.fetchSize) - - println(selectAll.toList().size) - - selectAll.map { "@${it[Actors.name]}@${it[Actors.domain]}" }.forEach { println(it) } - - return Actors.selectAll().where { Actors.name eq username and (Actors.domain eq s) }.empty().not() - } -} diff --git a/hideout-core/src/e2eTest/kotlin/KarateUtil.kt b/hideout-core/src/e2eTest/kotlin/KarateUtil.kt deleted file mode 100644 index 3d2b5604..00000000 --- a/hideout-core/src/e2eTest/kotlin/KarateUtil.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.intuit.karate.junit5.Karate - -object KarateUtil { - fun springBootKarateTest(path: String, scenario: String, clazz: Class<*>, port: String): Karate { - if (scenario.isEmpty()) { - return Karate.run(path).relativeTo(clazz).systemProperty("karate.port", port).karateEnv("dev") - } else { - return Karate.run(path).scenarioName(scenario).relativeTo(clazz).systemProperty("karate.port", port) - .karateEnv("dev") - } - } - - fun e2eTest(path: String, scenario: String = "", properties: Map, clazz: Class<*>): Karate { - val run = Karate.run(path) - - val karate = if (scenario.isEmpty()) { - run - } else { - run.scenarioName(scenario) - } - - var relativeTo = karate.relativeTo(clazz) - - properties.map { relativeTo = relativeTo.systemProperty(it.key, it.value) } - - return relativeTo.karateEnv("dev") - } -} diff --git a/hideout-core/src/e2eTest/kotlin/federation/FollowAcceptTest.kt b/hideout-core/src/e2eTest/kotlin/federation/FollowAcceptTest.kt deleted file mode 100644 index 765556cf..00000000 --- a/hideout-core/src/e2eTest/kotlin/federation/FollowAcceptTest.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 federation - -import AssertionUtil -import KarateUtil -import com.intuit.karate.core.MockServer -import com.intuit.karate.junit5.Karate -import dev.usbharu.hideout.SpringApplication -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.* -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.server.LocalServerPort -import org.springframework.transaction.annotation.Transactional -import java.net.MalformedURLException -import java.net.URL - -@SpringBootTest( - classes = [SpringApplication::class], - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT -) -@Transactional -class FollowAcceptTest { - @LocalServerPort - private var port = "" - - @Karate.Test - @TestFactory - @Disabled - fun `FollowAcceptTest`(): Karate { - return KarateUtil.e2eTest( - "FollowAcceptTest", "Follow Accept Test", - mapOf("karate.port" to port), - javaClass - ) - } - - companion object { - lateinit var server: MockServer - - lateinit var _remotePort: String - - @JvmStatic - fun assertUserExist(username: String, domain: String) = runBlocking { - val s = try { - val url = URL(domain) - url.host + ":" + url.port.toString().takeIf { it != "-1" }.orEmpty() - } catch (e: MalformedURLException) { - domain - } - - var check = false - - repeat(10) { - delay(1000) - check = AssertionUtil.assertUserExist(username, s) or check - if (check) { - return@repeat - } - } - - Assertions.assertTrue(check, "User @$username@$s not exist.") - } - - @JvmStatic - fun getRemotePort(): String = _remotePort - - @BeforeAll - @JvmStatic - fun beforeAll(@Autowired flyway: Flyway) { - server = MockServer.feature("classpath:federation/FollowAcceptMockServer.feature").http(0).build() - _remotePort = server.port.toString() - - flyway.clean() - flyway.migrate() - } - - @AfterAll - @JvmStatic - fun afterAll() { - server.stop() - } - } -} diff --git a/hideout-core/src/e2eTest/kotlin/federation/InboxCommonTest.kt b/hideout-core/src/e2eTest/kotlin/federation/InboxCommonTest.kt deleted file mode 100644 index 2e1ece7f..00000000 --- a/hideout-core/src/e2eTest/kotlin/federation/InboxCommonTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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 federation - -import AssertionUtil -import KarateUtil -import com.intuit.karate.core.MockServer -import com.intuit.karate.junit5.Karate -import dev.usbharu.hideout.SpringApplication -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.TestFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.server.LocalServerPort -import org.springframework.transaction.annotation.Transactional - -@SpringBootTest( - classes = [SpringApplication::class], - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT -) -@Transactional -@Disabled -class InboxCommonTest { - @LocalServerPort - private var port = "" - - @Karate.Test - @TestFactory - fun `inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く`(): Karate { - return KarateUtil.e2eTest( - "InboxCommonTest", - "inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く", - mapOf( - "karate.port" to port, - "karate.remotePort" to _remotePort - ), - javaClass - ) - } - - @Karate.Test - @TestFactory - fun `user-inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く`(): Karate { - return KarateUtil.e2eTest( - "InboxCommonTest", - "user-inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く", - mapOf( - "karate.port" to port, - "karate.remotePort" to _remotePort - ), - javaClass - ) - } - - @Karate.Test - @TestFactory - fun `inboxにHTTP Signatureがないリクエストがきたら401を返す`(): Karate { - return KarateUtil.e2eTest( - "InboxCommonTest", - "inboxにHTTP Signatureがないリクエストがきたら401を返す", - mapOf("karate.port" to port), - javaClass - ) - } - - @Karate.Test - @TestFactory - fun `user-inboxにHTTP Signatureがないリクエストがきたら401を返す`(): Karate { - return KarateUtil.e2eTest( - "InboxCommonTest", - "user-inboxにHTTP Signatureがないリクエストがきたら401を返す", - mapOf("karate.port" to port), - javaClass - ) - } - - @Karate.Test - @TestFactory - fun `inboxにConetnt-Type application *+json以外が来たら415を返す`(): Karate { - return KarateUtil.e2eTest( - "InboxCommonTest", - "inboxにContent-Type application/json以外が来たら415を返す", - mapOf("karate.port" to port), - javaClass - ) - } - - companion object { - lateinit var server: MockServer - - lateinit var _remotePort: String - - @JvmStatic - fun assertUserExist(username: String, domain: String) = runBlocking { - var check = false - - repeat(10) { - delay(1000) - check = AssertionUtil.assertUserExist(username, domain) or check - if (check) { - return@repeat - } - } - - assertTrue(check, "User @$username@$domain not exist.") - } - - @JvmStatic - fun getRemotePort(): String = _remotePort - - @BeforeAll - @JvmStatic - fun beforeAll() { - server = MockServer.feature("classpath:federation/InboxxCommonMockServerTest.feature").http(0).build() - _remotePort = server.port.toString() - } - - @AfterAll - @JvmStatic - fun afterAll(@Autowired flyway: Flyway) { - server.stop() - flyway.clean() - flyway.migrate() - } - } -} diff --git a/hideout-core/src/e2eTest/kotlin/oauth2/OAuth2LoginTest.kt b/hideout-core/src/e2eTest/kotlin/oauth2/OAuth2LoginTest.kt deleted file mode 100644 index fd248df1..00000000 --- a/hideout-core/src/e2eTest/kotlin/oauth2/OAuth2LoginTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 oauth2 - -import KarateUtil -import com.intuit.karate.junit5.Karate -import dev.usbharu.hideout.SpringApplication -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.TestFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.server.LocalServerPort -import org.springframework.test.context.jdbc.Sql - -@SpringBootTest( - classes = [SpringApplication::class], - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, -) -@Sql("/oauth2/user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class OAuth2LoginTest { - - @LocalServerPort - private var port = "" - - @Karate.Test - @TestFactory - fun `スコープwrite readを持ったトークンの作成`(): Karate { - return KarateUtil.springBootKarateTest( - "Oauth2LoginTest", - "スコープwrite readを持ったトークンの作成", - javaClass, - port - ) - } - - @Karate.Test - @TestFactory - fun `スコープread_statuses write_statusesを持ったトークンの作成`(): Karate { - return KarateUtil.springBootKarateTest( - "Oauth2LoginTest", - "スコープread:statuses write:statusesを持ったトークンの作成", - javaClass, - port - ) - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway) { - flyway.clean() - flyway.migrate() - } - } -} diff --git a/hideout-core/src/e2eTest/resources/application.yml b/hideout-core/src/e2eTest/resources/application.yml deleted file mode 100644 index 778035ba..00000000 --- a/hideout-core/src/e2eTest/resources/application.yml +++ /dev/null @@ -1,44 +0,0 @@ -hideout: - url: "https://localhost:8080" - use-mongodb: false - security: - jwt: - generate: true - key-id: a - private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" - public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" - storage: - type: local - debug: - trace-query-exception: true - trace-query-call: true - private: false - -spring: - flyway: - enabled: true - clean-disabled: false - datasource: - driver-class-name: org.h2.Driver - url: "jdbc:h2:mem:e2e-test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4" - username: "" - password: - data: - mongodb: - auto-index-creation: true - host: localhost - port: 27017 - database: hideout - h2: - console: - enabled: true - -# exposed: -# generate-ddl: true -# excluded-packages: dev.usbharu.hideout.core.infrastructure.kjobexposed -server: - port: 8080 - tomcat: - basedir: tomcat-e2e - accesslog: - enabled: true diff --git a/hideout-core/src/e2eTest/resources/federation/FollowAcceptMockServer.feature b/hideout-core/src/e2eTest/resources/federation/FollowAcceptMockServer.feature deleted file mode 100644 index 60793fde..00000000 --- a/hideout-core/src/e2eTest/resources/federation/FollowAcceptMockServer.feature +++ /dev/null @@ -1,140 +0,0 @@ -Feature: Follow Accept Mock Server - - Background: - * def assertInbox = Java.type(`federation.FollowAcceptTest`) - * def req = {req: []} - - Scenario: pathMatches('/users/test-follower') && methodIs('get') - * def remoteUrl = 'http://localhost:' + assertInbox.getRemotePort() - * def username = 'test-follower' - * def userUrl = remoteUrl + '/users/' + username - - - * def person = - """ - { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "toot": "http://joinmastodon.org/ns#", - "featured": { - "@id": "toot:featured", - "@type": "@id" - }, - "featuredTags": { - "@id": "toot:featuredTags", - "@type": "@id" - }, - "alsoKnownAs": { - "@id": "as:alsoKnownAs", - "@type": "@id" - }, - "movedTo": { - "@id": "as:movedTo", - "@type": "@id" - }, - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "discoverable": "toot:discoverable", - "Device": "toot:Device", - "Ed25519Signature": "toot:Ed25519Signature", - "Ed25519Key": "toot:Ed25519Key", - "Curve25519Key": "toot:Curve25519Key", - "EncryptedMessage": "toot:EncryptedMessage", - "publicKeyBase64": "toot:publicKeyBase64", - "deviceId": "toot:deviceId", - "claim": { - "@type": "@id", - "@id": "toot:claim" - }, - "fingerprintKey": { - "@type": "@id", - "@id": "toot:fingerprintKey" - }, - "identityKey": { - "@type": "@id", - "@id": "toot:identityKey" - }, - "devices": { - "@type": "@id", - "@id": "toot:devices" - }, - "messageFranking": "toot:messageFranking", - "messageType": "toot:messageType", - "cipherText": "toot:cipherText", - "suspended": "toot:suspended", - "focalPoint": { - "@container": "@list", - "@id": "toot:focalPoint" - } - } - ], - "id": #(userUrl), - "type": "Person", - "following": #(userUrl + '/following'), - "followers": #(userUrl + '/followers'), - "inbox": #(userUrl + '/inbox'), - "outbox": #(userUrl + '/outbox'), - "featured": #(userUrl + '/collections/featured'), - "featuredTags": #(userUrl + '/collections/tags'), - "preferredUsername": #(username), - "name": #(username), - "summary": "E2E Test User Jaga/Cotlin/Winter Boot/Ktol\nYonTude: https://example.com\nY(Tvvitter): https://example.com\n", - "url": #(userUrl + '/@' + username), - "manuallyApprovesFollowers": false, - "discoverable": true, - "published": "2016-03-16T00:00:00Z", - "devices": #(userUrl + '/collections/devices'), - "alsoKnownAs": [ - #( 'https://example.com/users/' + username) - ], - "publicKey": { - "id": #(userUrl + '#main-key'), - "owner": #(userUrl), - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmtKo0xYGXR4M0LQQhK4\nBkKpRvvUxrqGV6Ew4CBHSyzdnbFsiBqHUWz4JvRiQvAPqQ4jQFpxVZCPr9xx6lJp\nx7EAAKIdVTnBBV4CYfu7QPsRqtjbB5408q+mo5oUXNs8xg2tcC42p2SJ5CRJX/fr\nOgCZwo3LC9pOBdCQZ+tiiPmWNBTNby99JZn4D/xNcwuhV04qcPoHYD9OPuxxGyzc\naVJ2mqJmvi/lewQuR8qnUIbz+Gik+xvyG6LmyuDoa1H2LDQfQXYb62G70HsYdu7a\ndObvZovytp+kkjP/cUaIYkhhOAYqAA4zCwVRY4NHK0MAMq9sMoUfNJa8U+zR9NvD\noQIDAQAB\n-----END PUBLIC KEY-----\n" - }, - "tag": [], - "attachment": [ - { - "type": "PropertyValue", - "name": "Pixib Fan-Bridge", - "value": "\u003ca href=\"https://example.com/hideout\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003eexample.com/hideout\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - }, - { - "type": "PropertyValue", - "name": "GitHub", - "value": "\u003ca href=\"https://github.com/usbharu/hideout\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/usbharu/hideout\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - } - ], - "endpoints": { - "sharedInbox": #(remoteUrl + '/inbox') - }, - "icon": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Destroyer_Vozbuzhdenyy.jpg/320px-Destroyer_Vozbuzhdenyy.jpg" - }, - "image": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Views_of_Mount_Fuji_from_%C5%8Cwakudani_20211202.jpg/320px-Views_of_Mount_Fuji_from_%C5%8Cwakudani_20211202.jpg" - } -} - """ - - * set req.req[] = '/users/' + username - * def response = person - - Scenario: pathMatches('/inbox') && methodIs('post') - * set req.req[] = '/inbox' - * def responseStatus = 202 - - Scenario: pathMatches('/internal-assertion-api/requests') && methodIs('get') - * def response = req - - Scenario: pathMatches('/internal-assertion-api/requests/deleteAll') && methodIs('post') - * set req.req = [] - * def responseStatus = 200 diff --git a/hideout-core/src/e2eTest/resources/federation/FollowAcceptTest.feature b/hideout-core/src/e2eTest/resources/federation/FollowAcceptTest.feature deleted file mode 100644 index 7cdb39e5..00000000 --- a/hideout-core/src/e2eTest/resources/federation/FollowAcceptTest.feature +++ /dev/null @@ -1,29 +0,0 @@ -Feature: Follow Accept Test - - Background: - * url baseUrl - * def assertionUtil = Java.type('AssertionUtil') - - Scenario: Follow Accept Test - - * def follow = - """ - {"type": "Follow","actor": #(remoteUrl + '/users/test-follower'),"object": #(baseUrl + '/users/test-user')} - """ - - Given path '/inbox' - And header Signature = 'keyId="https://test-hideout.usbharu.dev/users/c#pubkey", algorithm="rsa-sha256", headers="x-request-id tpp-redirect-uri digest psu-id", signature="e/91pFiI5bRffP33EMrqoI5A0xjkg3Ar0kzRGHC/1RsLrDW0zV50dHly/qJJ5xrYHRlss3+vd0mznTLBs1X0hx0uXjpfvCvwclpSi8u+sqn+Y2bcQKzf7ah0vAONQd6zeTYW7e/1kDJreP43PsJyz29KZD16Yop8nM++YeEs6C5eWiyYXKoQozXnfmTOX3y6bhxfKKQWVcxA5aLOTmTZRYTsBsTy9zn8NjDQaRI/0gcyYPqpq+2g8j2DbyJu3Z6zP6VmwbGGlQU/s9Pa7G5LqUPH/sBMSlIeqh+Hvm2pL7B3/BMFvGtTD+e2mR60BFnLIxMYx+oX4o33J2XkFIODLQ=="' - And request follow - When method post - Then status 202 - - And retry until assertionUtil.assertUserExist('test-follower',remoteUrl) - - * url remoteUrl - - Given path '/internal-assertion-api/requests' - When method get - Then status 200 - And match response.req contains ['/users/test-follower'] - - * url baseUrl diff --git a/hideout-core/src/e2eTest/resources/federation/InboxCommonTest.feature b/hideout-core/src/e2eTest/resources/federation/InboxCommonTest.feature deleted file mode 100644 index 848e5630..00000000 --- a/hideout-core/src/e2eTest/resources/federation/InboxCommonTest.feature +++ /dev/null @@ -1,158 +0,0 @@ -Feature: Inbox Common Test - - Background: - * url baseUrl - - Scenario: inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く - - * url remoteUrl - - Given path '/internal-assertion-api/requests/deleteAll' - When method post - Then status 200 - - * url baseUrl - - * def inbox = - """ - { "type": "Follow" } - """ - - Given path `/inbox` - And request inbox -# And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target)", signature="a"' - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - When method post - Then status 202 - - * def assertInbox = Java.type(`federation.InboxCommonTest`) - - And assertInbox.assertUserExist('test-user',remoteUrl) - - * url remoteUrl - - Given path '/internal-assertion-api/requests' - When method get - Then status 200 - - * url baseUrl - - * print response - Then match response.req == ['/users/test-user'] - - - Scenario: inboxにHTTP Signatureがないリクエストがきたら401を返す - - * def inbox = - """ - {"type": "Follow"} - """ - - Given path '/inbox' - And request inbox - When method post - Then status 401 - - - Scenario: user-inboxにHTTP Signature付きのリクエストがあったらリモートに取得しに行く - - * url remoteUrl - - Given path '/internal-assertion-api/requests/deleteAll' - When method post - Then status 200 - - * url baseUrl - - * def inbox = - """ - { "type": "Follow" } - """ - - Given path `/inbox` - And request inbox -# And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target)", signature="a"' - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user2#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - When method post - Then status 202 - - * def assertInbox = Java.type(`federation.InboxCommonTest`) - - And assertInbox.assertUserExist('test-user2',remoteUrl) - - - * url remoteUrl - - Given path '/internal-assertion-api/requests' - When method get - Then status 200 - - * url baseUrl - - * print response - Then match response.req == ['/users/test-user2'] - - Scenario: user-inboxにHTTP Signatureがないリクエストがきたら401を返す - - * def inbox = - """ - {"type": "Follow"} - """ - - Given path '/inbox' - And request inbox - When method post - Then status 401 - - - Scenario: inboxにContent-Type application/json以外が来たら415を返す - - * def inbox = - """ - {"type": "Follow"} - """ - - Given path '/inbox' - And request inbox - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - And header Accept = 'application/activity+json' - And header Content-Type = 'application/json' - When method post - Then status 202 - - Given path '/inbox' - And request inbox - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - And header Accept = 'application/activity+json' - And header Content-Type = 'application/activity+json' - When method post - Then status 202 - - Given path '/inbox' - And request inbox - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - And header Accept = 'application/activity+json' - And header Content-Type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' - When method post - Then status 202 - - Given path '/inbox' - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - And header Accept = 'application/activity+json' - When method post - Then status 415 - - * def html = - """ - - - -""" - - Given path '/inbox' - And header Signature = 'keyId="'+ remoteUrl +'/users/test-user#pubkey", algorithm="rsa-sha256", headers="(request-target) date host digest", signature="FfpkmBogW70FMo94yovGpl15L/m4bDjVIFb9mSZUstPE3H00nHiqNsjAq671qFMJsGOO1uWfLEExcdvzwTiC3wuHShzingvxQUbTgcgRTRZcHbtrOZxT8hYHGndpCXGv/NOLkfXDtZO9v5u0fnA2yJFokzyPHOPJ1cJliWlXP38Bl/pO4H5rBLQBZKpM2jYIjMyI78G2rDXNHEeGrGiyfB5SKb3H6zFQL+X9QpXUI4n0f07VsnwaDyp63oUopmzNUyBEuSqB+8va/lbfcWwrxpZnKGzQRZ+VBcV7jDoKGNOP9/O1xEI2CwB8sh+h6KVHdX3EQEvO1slaaLzcwRRqrQ=="' - And header Accept = 'application/activity+json' - And header Content-Type = 'text/html' - And request html - When method post - Then status 415 diff --git a/hideout-core/src/e2eTest/resources/federation/InboxxCommonMockServerTest.feature b/hideout-core/src/e2eTest/resources/federation/InboxxCommonMockServerTest.feature deleted file mode 100644 index 6d114c04..00000000 --- a/hideout-core/src/e2eTest/resources/federation/InboxxCommonMockServerTest.feature +++ /dev/null @@ -1,136 +0,0 @@ -Feature: InboxCommonMockServer - - Background: - * def assertInbox = Java.type(`federation.InboxCommonTest`) - * def req = {req: []} - - Scenario: pathMatches('/users/{username}') && methodIs('get') - * def remoteUrl = 'http://localhost:' + assertInbox.getRemotePort() - * def username = pathParams.username - * def userUrl = remoteUrl + '/users/' + username - - - * def person = - """ -{ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "toot": "http://joinmastodon.org/ns#", - "featured": { - "@id": "toot:featured", - "@type": "@id" - }, - "featuredTags": { - "@id": "toot:featuredTags", - "@type": "@id" - }, - "alsoKnownAs": { - "@id": "as:alsoKnownAs", - "@type": "@id" - }, - "movedTo": { - "@id": "as:movedTo", - "@type": "@id" - }, - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "discoverable": "toot:discoverable", - "Device": "toot:Device", - "Ed25519Signature": "toot:Ed25519Signature", - "Ed25519Key": "toot:Ed25519Key", - "Curve25519Key": "toot:Curve25519Key", - "EncryptedMessage": "toot:EncryptedMessage", - "publicKeyBase64": "toot:publicKeyBase64", - "deviceId": "toot:deviceId", - "claim": { - "@type": "@id", - "@id": "toot:claim" - }, - "fingerprintKey": { - "@type": "@id", - "@id": "toot:fingerprintKey" - }, - "identityKey": { - "@type": "@id", - "@id": "toot:identityKey" - }, - "devices": { - "@type": "@id", - "@id": "toot:devices" - }, - "messageFranking": "toot:messageFranking", - "messageType": "toot:messageType", - "cipherText": "toot:cipherText", - "suspended": "toot:suspended", - "focalPoint": { - "@container": "@list", - "@id": "toot:focalPoint" - } - } - ], - "id": #(userUrl), - "type": "Person", - "following": #(userUrl + '/following'), - "followers": #(userUrl + '/followers'), - "inbox": #(userUrl + '/inbox'), - "outbox": #(userUrl + '/outbox'), - "featured": #(userUrl + '/collections/featured'), - "featuredTags": #(userUrl + '/collections/tags'), - "preferredUsername": #(username), - "name": #(username), - "summary": "E2E Test User Jaga/Cotlin/Winter Boot/Ktol\nYonTude: https://example.com\nY(Tvvitter): https://example.com\n", - "url": #(userUrl + '/@' + username), - "manuallyApprovesFollowers": false, - "discoverable": true, - "published": "2016-03-16T00:00:00Z", - "devices": #(userUrl + '/collections/devices'), - "alsoKnownAs": [ - #( 'https://example.com/users/' + username) - ], - "publicKey": { - "id": #(userUrl + '#main-key'), - "owner": #(userUrl), - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmtKo0xYGXR4M0LQQhK4\nBkKpRvvUxrqGV6Ew4CBHSyzdnbFsiBqHUWz4JvRiQvAPqQ4jQFpxVZCPr9xx6lJp\nx7EAAKIdVTnBBV4CYfu7QPsRqtjbB5408q+mo5oUXNs8xg2tcC42p2SJ5CRJX/fr\nOgCZwo3LC9pOBdCQZ+tiiPmWNBTNby99JZn4D/xNcwuhV04qcPoHYD9OPuxxGyzc\naVJ2mqJmvi/lewQuR8qnUIbz+Gik+xvyG6LmyuDoa1H2LDQfQXYb62G70HsYdu7a\ndObvZovytp+kkjP/cUaIYkhhOAYqAA4zCwVRY4NHK0MAMq9sMoUfNJa8U+zR9NvD\noQIDAQAB\n-----END PUBLIC KEY-----\n" - }, - "tag": [], - "attachment": [ - { - "type": "PropertyValue", - "name": "Pixib Fan-Bridge", - "value": "\u003ca href=\"https://example.com/hideout\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003eexample.com/hideout\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - }, - { - "type": "PropertyValue", - "name": "GitHub", - "value": "\u003ca href=\"https://github.com/usbharu/hideout\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/usbharu/hideout\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - } - ], - "endpoints": { - "sharedInbox": #(remoteUrl + '/inbox') - }, - "icon": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Destroyer_Vozbuzhdenyy.jpg/320px-Destroyer_Vozbuzhdenyy.jpg" - }, - "image": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Views_of_Mount_Fuji_from_%C5%8Cwakudani_20211202.jpg/320px-Views_of_Mount_Fuji_from_%C5%8Cwakudani_20211202.jpg" - } -} - - """ - * set req.req[] = '/users/' + username - * def response = person - - Scenario: pathMatches('/internal-assertion-api/requests') && methodIs('get') - * def response = req - - Scenario: pathMatches('/internal-assertion-api/requests/deleteAll') && methodIs('post') - * set req.req = [] - * def responseStatus = 200 diff --git a/hideout-core/src/e2eTest/resources/karate-config.js b/hideout-core/src/e2eTest/resources/karate-config.js deleted file mode 100644 index a83c2bb4..00000000 --- a/hideout-core/src/e2eTest/resources/karate-config.js +++ /dev/null @@ -1,30 +0,0 @@ -function fn() { - var env = karate.env; // get java system property 'karate.env' - karate.log('karate.env system property was:', env); - if (!env) { - env = 'dev'; // a custom 'intelligent' default - karate.log('karate.env set to "dev" as default.'); - } - let config; - if (env === 'test') { - let remotePort = karate.properties['karate.remotePort'] || '8081' - config = { - baseUrl: 'https://test-hideout.usbharu.dev', - remoteUrl: 'http://localhost:' + remotePort - } - } else if (env === 'dev') { - let port = karate.properties['karate.port'] || '8080' - let remotePort = karate.properties['karate.remotePort'] || '8081' - config = { - baseUrl: 'http://localhost:' + port, - remoteUrl: 'http://localhost:' + remotePort - } - } else { - throw 'Unknown environment [' + env + '].' - } - // don't waste time waiting for a connection or if servers don't respond within 0,3 seconds - - karate.configure('connectTimeout', 1000); - karate.configure('readTimeout', 1000); - return config; -} diff --git a/hideout-core/src/e2eTest/resources/logback.xml b/hideout-core/src/e2eTest/resources/logback.xml deleted file mode 100644 index c21752ee..00000000 --- a/hideout-core/src/e2eTest/resources/logback.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - ./e2eTest.log - - UTF-8 - %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n - - - - - %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n - - - - - - - - - - diff --git a/hideout-core/src/e2eTest/resources/oauth2/Oauth2LoginTest.feature b/hideout-core/src/e2eTest/resources/oauth2/Oauth2LoginTest.feature deleted file mode 100644 index af203344..00000000 --- a/hideout-core/src/e2eTest/resources/oauth2/Oauth2LoginTest.feature +++ /dev/null @@ -1,95 +0,0 @@ -Feature: OAuth2 Login Test - - Background: - * url baseUrl - * configure driver = { type: 'chrome',start: true, headless: true, showDriverLog: true, addOptions: [ '--headless=new' ] } - - Scenario: スコープwrite readを持ったトークンの作成 - - * def apps = - """ - { - "client_name": "oauth2-test-client-1", - "redirect_uris": "https://usbharu.dev", - "scopes": "write read" - } - """ - - Given path '/api/v1/apps' - And request apps - When method post - Then status 200 - - * def client_id = response.client_id - * def client_secret = response.client_secret - - * def authorizeEndpoint = baseUrl + '/oauth/authorize?response_type=code&redirect_uri=https://usbharu.dev&client_id=' + client_id + '&scope=write%20read' - - Given driver authorizeEndpoint - And driver.input('#username','test-user') - And driver.input('#password','password') - - When driver.submit().click('body > div > form > button') - Then driver.waitForUrl(authorizeEndpoint + "&continue") - And driver.click('#read') - And driver.click('#write') - - When driver.submit().click('#submit-consent') - Then driver.waitUntil("location.host == 'usbharu.dev'") - - * def code = script("new URLSearchParams(document.location.search).get('code')") - - Given path '/oauth/token' - And form field client_id = client_id - And form field client_secret = client_secret - And form field redirect_uri = 'https://usbharu.dev' - And form field grant_type = 'authorization_code' - And form field code = code - And form field scope = 'write read' - When method post - Then status 200 - - Scenario: スコープread:statuses write:statusesを持ったトークンの作成 - - * def apps = - """ - { - "client_name": "oauth2-test-client-2", - "redirect_uris": "https://usbharu.dev", - "scopes": "read:statuses write:statuses" - } - """ - - Given path '/api/v1/apps' - And request apps - When method post - Then status 200 - - * def client_id = response.client_id - * def client_secret = response.client_secret - - * def authorizeEndpoint = baseUrl + '/oauth/authorize?response_type=code&redirect_uri=https://usbharu.dev&client_id=' + client_id + '&scope=read:statuses+write:statuses' - - Given driver authorizeEndpoint - And driver.input('#username','test-user') - And driver.input('#password','password') - - When driver.submit().click('body > div > form > button') - Then driver.waitForUrl(authorizeEndpoint + "&continue") - And driver.click('/html/body/div/div[4]/div/form/div[1]/input') - And driver.click('/html/body/div/div[4]/div/form/div[2]/input') - - When driver.submit().click('#submit-consent') - Then driver.waitUntil("location.host == 'usbharu.dev'") - - * def code = script("new URLSearchParams(document.location.search).get('code')") - - Given path '/oauth/token' - And form field client_id = client_id - And form field client_secret = client_secret - And form field redirect_uri = 'https://usbharu.dev' - And form field grant_type = 'authorization_code' - And form field code = code - And form field scope = 'write read' - When method post - Then status 200 diff --git a/hideout-core/src/e2eTest/resources/oauth2/user.sql b/hideout-core/src/e2eTest/resources/oauth2/user.sql deleted file mode 100644 index 4184f284..00000000 --- a/hideout-core/src/e2eTest/resources/oauth2/user.sql +++ /dev/null @@ -1,51 +0,0 @@ -insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, - key_id, following, followers, instance, locked, following_count, followers_count, posts_count, - last_post_at) -VALUES (1730415786666758144, 'test-user', 'localhost', 'Im test user.', 'THis account is test user.', - 'http://localhost/users/test-user/inbox', - 'http://localhost/users/test-user/outbox', 'http://localhost/users/test-user', - '-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi4mifRg6huAIn6DXk3Vn -5tkRC0AO32ZJvczwXr9xDj4HJvrSUHBAxIwwIeuCceAYtiuZk4JmEKydeB6SRkoO -Nty93XZXS1SMmiHCvWOY5YlpnfFU1kLqW3fkXcLNls4XmzujLt1i2sT8mYkENAsP -h6K4SRtmktOVYZOWcVEcfLGKbJvaDD/+lKikNC1XCouylfGV/bA/FPY5vuI+7cdM -Mjana28JdiWlPWSdzcxtCSgN+nGWPjk2WWm8K+wK2zXqMxA0U0p4odyyILBGALxX -zMqObIQvpwPh/t+b6ohem4eq70/0/SwDhd+IzHkT3x4UzG1oxSQS/juPkO7uuS8p -uwIDAQAB ------END PUBLIC KEY----- -', - '-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCLiaJ9GDqG4Aif -oNeTdWfm2RELQA7fZkm9zPBev3EOPgcm+tJQcEDEjDAh64Jx4Bi2K5mTgmYQrJ14 -HpJGSg423L3ddldLVIyaIcK9Y5jliWmd8VTWQupbd+Rdws2WzhebO6Mu3WLaxPyZ -iQQ0Cw+HorhJG2aS05Vhk5ZxURx8sYpsm9oMP/6UqKQ0LVcKi7KV8ZX9sD8U9jm+ -4j7tx0wyNqdrbwl2JaU9ZJ3NzG0JKA36cZY+OTZZabwr7ArbNeozEDRTSnih3LIg -sEYAvFfMyo5shC+nA+H+35vqiF6bh6rvT/T9LAOF34jMeRPfHhTMbWjFJBL+O4+Q -7u65Lym7AgMBAAECggEADJLa7v3LbFLsxAGY22NFeRJPTF252VycwXshn9ANbnSd -bWBFqlTrKSrevXe82ekRIP09ygKCkvcS+3t5v9a1gDEU9MtQo2ubfdoT87/xS6G9 -wCs6c1I1Twe3LtG6d9/bVbQiiLsPSNpeTrF/jPcAL780bvYGoK1rNQ85C7383Kl6 -1nwZCD0itjkmzbO0nGMRCduW46OdQKiOMuEC7z0zwynH3cK3wGvdlKyLG4L3pPZm -1/Uz7AZTieqSCjSgcgmaut7dmS49e3j8ujfb3wcKscfHoofyqNWsW1xyU1WytO9a -QLh9wlqfvGlfwQWkY6z6uFmc4XfRVZSC8nic4cAW3QKBgQC4PYbR5AuylDcfc6Am -jpL5mcF6qEMnEPgnL9z5VvuLs1f/JEyx5VgzQreDOKc1KOxDX7Xhok4gpvIJv1fi -zimviszEmIpHdPvgS7mP2hu42bSIjwVaXpny5aEEZbB6HQ9pGDW/MSsgmb6x31Kx -o+sslpqf9cpalI35UPtkNaEJNwKBgQDB4tVUQ5gGPKllEfCN64B/B7wodWr5cUNU -UpUXdFPCu+HXnRen6GKLo+25wmCUGtcIuvCY1Xm+tL0Z7jrI+oOD4CL9ob7BJrPF -XCq0jUhaEzWFGp1SOa6n+35fWPkCfG4EwfsK8+PWoZsZc1eykMxIJmBln3vufuHz -qybfhy0VnQKBgD2tAxvyXmQar9VMjLk7k0IRUa6w80H5sUjVAgFKOA0NLZEQ4sfO -wdbvJ6W66mamW2k2ehmdjs/pcy8GKfKYF2ZXbbMGaYwAQm1UjDr2xb78yi3Iyv70 -mk6wxlVFgW1vmwAQhbWKTSitryO2YeVrvUeA5yRTULk/78Mdc/qY5V7DAoGAAu3I -RzOWMlHsRSiWN66dDE4zm3DaotYBLF7q/aW2NjTcXoNy/ghWpMFfL/UtvE8DfJBG -XiirZCQazy94F90g63cRUD+HQCezg4G2629O7n1ny5DxW3Kfns3/xLT1XgI/Lzc2 -8Z1pja53R1Ukt//T9isOPbrBBoNIKoQlXC8QkUkCgYEAsib3uOMAIOJab5jc8FSj -VG+Cg2H63J5DgUUwx2Y0DPENugdGyYzCDMVPBNaB0Ru1SpqbUjgqh+YHynunSVeu -hDXMOteeyeVHUGw8mvcCEt53uRYVNW/rzXTMqfLVxbsJZHCsJBtFpwcgD2w4NjS2 -Ja15+ZWbOA4vJA9pOh3x4XM= ------END PRIVATE KEY----- -', 1701398248417, - 'http://localhost/users/test-user#pubkey', 'http://localhost/users/test-user/following', - 'http://localhost/users/test-users/followers', 0, false, 0, 0, 0, null); - -insert into user_details (actor_id, password, auto_accept_followee_follow_request) -values ( 1730415786666758144 - , '$2a$10$/mWC/n7nC7X3l9qCEOKnredxne2zewoqEsJWTOdlKfg2zXKJ0F9Em', true) diff --git a/hideout-core/src/intTest/kotlin/activitypub/inbox/InboxTest.kt b/hideout-core/src/intTest/kotlin/activitypub/inbox/InboxTest.kt deleted file mode 100644 index 40f9ffdf..00000000 --- a/hideout-core/src/intTest/kotlin/activitypub/inbox/InboxTest.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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 activitypub.inbox - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.util.Base64Util -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.http.MediaType -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext -import util.TestTransaction -import util.WithMockHttpSignature -import java.security.MessageDigest -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -class InboxTest { - - @Autowired - @Qualifier("http") - private lateinit var dateTimeFormatter: DateTimeFormatter - - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(springSecurity()) - .build() - } - - @Test - @WithAnonymousUser - fun `匿名でinboxにPOSTしたら401`() { - mockMvc - .post("/inbox") { - content = "{}" - contentType = MediaType.APPLICATION_JSON - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header( - "Digest", - "SHA-256=" + Base64Util.encode(MessageDigest.getInstance("SHA-256").digest("{}".toByteArray())) - ) - } - .asyncDispatch() - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithMockHttpSignature - fun 有効なHttpSignatureでPOSTしたら202() { - mockMvc - .post("/inbox") { - content = "{}" - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header( - "Digest", - "SHA-256=" + Base64Util.encode(MessageDigest.getInstance("SHA-256").digest("{}".toByteArray())) - ) - } - .asyncDispatch() - .andExpect { status { isAccepted() } } - } - - @Test - @WithAnonymousUser - fun `匿名でuser-inboxにPOSTしたら401`() { - mockMvc - .post("/users/hoge/inbox") { - content = "{}" - contentType = MediaType.APPLICATION_JSON - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header( - "Digest", - "SHA-256=" + Base64Util.encode(MessageDigest.getInstance("SHA-256").digest("{}".toByteArray())) - ) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithMockHttpSignature - fun 有効なHttpSignaturesでPOSTしたら202() { - mockMvc - .post("/users/hoge/inbox") { - content = "{}" - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header( - "Digest", - "SHA-256=" + Base64Util.encode(MessageDigest.getInstance("SHA-256").digest("{}".toByteArray())) - ) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isAccepted() } } - } - - @TestConfiguration - class Configuration { - @Bean - fun testTransaction() = TestTransaction - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/activitypub/note/NoteTest.kt b/hideout-core/src/intTest/kotlin/activitypub/note/NoteTest.kt deleted file mode 100644 index 62d7d3ce..00000000 --- a/hideout-core/src/intTest/kotlin/activitypub/note/NoteTest.kt +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 activitypub.note - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.MediaType -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext -import util.WithHttpSignature -import util.WithMockHttpSignature -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -class NoteTest { - private lateinit var mockMvc: MockMvc - - @Autowired - private lateinit var context: WebApplicationContext - - @Autowired - @Qualifier("http") - private lateinit var dateTimeFormatter: DateTimeFormatter - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build() - } - - @Test - @WithAnonymousUser - @Sql("/sql/note/匿名でpublic投稿を取得できる.sql") - fun `匿名でpublic投稿を取得できる`() { - mockMvc - .get("/users/test-user/posts/1234") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.to") { value("https://www.w3.org/ns/activitystreams#Public") } } - .andExpect { jsonPath("\$.cc") { value("https://www.w3.org/ns/activitystreams#Public") } } - } - - @Test - @Sql("/sql/note/匿名でunlisted投稿を取得できる.sql") - @WithAnonymousUser - fun 匿名でunlisted投稿を取得できる() { - mockMvc - .get("/users/test-user2/posts/1235") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.to") { value("https://example.com/users/test-user2/followers") } } - .andExpect { jsonPath("\$.cc") { value("https://www.w3.org/ns/activitystreams#Public") } } - } - - @Test - @Transactional - @WithAnonymousUser - @Sql("/sql/note/匿名でfollowers投稿を取得しようとすると404.sql") - fun 匿名でfollowers投稿を取得しようとすると404() { - mockMvc - .get("/users/test-user2/posts/1236") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andExpect { status { isNotFound() } } - } - - @Test - @WithAnonymousUser - fun 匿名でdirect投稿を取得しようとすると404() { - mockMvc - .get("/users/test-user2/posts/1236") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andExpect { status { isNotFound() } } - } - - @Test - @Sql("/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql") - @WithHttpSignature(keyId = "https://follower.example.com/users/test-user5#pubkey") - fun HttpSignature認証でフォロワーがpublic投稿を取得できる() { - mockMvc - .get("/users/test-user4/posts/1237") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.to") { value("https://www.w3.org/ns/activitystreams#Public") } } - .andExpect { jsonPath("\$.cc") { value("https://www.w3.org/ns/activitystreams#Public") } } - } - - @Test - @Sql("/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql") - @WithHttpSignature(keyId = "https://follower.example.com/users/test-user7#pubkey") - fun httpSignature認証でフォロワーがunlisted投稿を取得できる() { - mockMvc - .get("/users/test-user6/posts/1238") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.to") { value("https://example.com/users/test-user6/followers") } } - .andExpect { jsonPath("\$.cc") { value("https://www.w3.org/ns/activitystreams#Public") } } - } - - @Test - @Sql("/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql") - @WithHttpSignature(keyId = "https://follower.example.com/users/test-user9#pubkey") - fun httpSignature認証でフォロワーがfollowers投稿を取得できる() { - mockMvc - .get("/users/test-user8/posts/1239") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.to") { value("https://example.com/users/test-user8/followers") } } - .andExpect { jsonPath("\$.cc") { value("https://example.com/users/test-user8/followers") } } - - } - - @Test - @Sql("/sql/note/リプライになっている投稿はinReplyToが存在する.sql") - @WithMockHttpSignature - fun リプライになっている投稿はinReplyToが存在する() { - mockMvc - .get("/users/test-user10/posts/1241") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.inReplyTo") { value("https://example.com/users/test-user10/posts/1240") } } - } - - @Test - @Sql("/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql") - @WithMockHttpSignature - fun メディア付き投稿はattachmentにDocumentとして画像が存在する() { - mockMvc - .get("/users/test-user10/posts/1242") { - accept(MediaType("application", "activity+json")) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { contentType("application/activity+json") } } - .andExpect { jsonPath("\$.type") { value("Note") } } - .andExpect { jsonPath("\$.attachment") { isArray() } } - .andExpect { jsonPath("\$.attachment[0].type") { value("Document") } } - .andExpect { jsonPath("\$.attachment[0].url") { value("https://example.com/media/test-media.png") } } - .andExpect { jsonPath("\$.attachment[1].type") { value("Document") } } - .andExpect { jsonPath("\$.attachment[1].url") { value("https://example.com/media/test-media2.png") } } - } - - @Test - fun signatureヘッダーがあるのにhostヘッダーがないと401() { - mockMvc - .get("/users/test-user10/posts/9999") { - accept(MediaType("application", "activity+json")) - header("Signature", "a") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - - } - .andExpect { status { isUnauthorized() } } - } - - @Test - fun signatureヘッダーがあるのにdateヘッダーがないと401() { - mockMvc - .get("/users/test-user10/posts/9999") { - accept(MediaType("application", "activity+json")) - header("Signature", "a") - header("Host", "example.com") - } - .andExpect { status { isUnauthorized() } } - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/activitypub/webfinger/WebFingerTest.kt b/hideout-core/src/intTest/kotlin/activitypub/webfinger/WebFingerTest.kt deleted file mode 100644 index a12bbfd4..00000000 --- a/hideout-core/src/intTest/kotlin/activitypub/webfinger/WebFingerTest.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 activitypub.webfinger - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.transaction.annotation.Transactional -import util.TestTransaction -import java.net.URL - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -class WebFingerTest { - @Autowired - private lateinit var mockMvc: MockMvc - - @Test - @Sql("/sql/test-user.sql") - - fun `webfinger 存在するユーザーを取得`() { - mockMvc - .get("/.well-known/webfinger?resource=acct:test-user@example.com") - .andExpect { status { isOk() } } - .andExpect { header { string("Content-Type", "application/json") } } - .andExpect { - jsonPath("\$.subject") { - value("acct:test-user@example.com") - } - } - .andExpect { - jsonPath("\$.links[0].rel") { - value("self") - } - } - .andExpect { - jsonPath("\$.links[0].href") { value("https://example.com/users/test-user") } - } - .andExpect { - jsonPath("\$.links[0].type") { - value("application/activity+json") - } - } - } - - @Test - fun `webfinger 存在しないユーザーに404`() { - mockMvc - .get("/.well-known/webfinger?resource=acct:invalid-user-notfound-afdjashfal@example.com") - .andExpect { status { isNotFound() } } - } - - @Test - fun `webfinger 不正なリクエストは400`() { - mockMvc - .get("/.well-known/webfinger?res=acct:test") - .andExpect { status { isBadRequest() } } - } - - @Test - fun `webfinger acctのパースが出来なくても400`() { - mockMvc - .get("/.well-known/webfinger?resource=acct:@a@b@c@d") - .andExpect { status { isBadRequest() } } - } - - @TestConfiguration - class Configuration { - @Bean - fun url(): URL { - return URL("https://example.com") - } - - @Bean - fun testTransaction(): Transaction = TestTransaction - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiPaginationTest.kt b/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiPaginationTest.kt deleted file mode 100644 index 81971e7a..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiPaginationTest.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 mastodon.account - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThat -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-user2.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/accounts/test-accounts-statuses.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class AccountApiPaginationTest { - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @Test - fun `apiV1AccountsIdStatusesGet 投稿を取得できる`() { - val content = mockMvc - .get("/api/v1/accounts/1/statuses"){ - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("100") - assertThat(value.last().id).isEqualTo("81") - assertThat(value).size().isEqualTo(20) - } - - @Test - fun `apiV1AccountsIdStatusesGet 結果が0件のときはLinkヘッダーがない`() { - val content = mockMvc - .get("/api/v1/accounts/1/statuses?min_id=100"){ - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { header { doesNotExist("Link") } } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - - assertThat(value).isEmpty() - } - - @Test - fun `apiV1AccountsIdStatusesGet maxIdを指定して取得`() { - val content = mockMvc - .get("/api/v1/accounts/1/statuses?max_id=100"){ - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("99") - assertThat(value.last().id).isEqualTo("80") - assertThat(value).size().isEqualTo(20) - } - - @Test - fun `apiV1AccountsIdStatusesGet minIdを指定して取得`() { - val content = mockMvc - .get("/api/v1/accounts/1/statuses?min_id=1"){ - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("21") - assertThat(value.last().id).isEqualTo("2") - assertThat(value).size().isEqualTo(20) - } - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - - } -} \ No newline at end of file diff --git a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt b/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt deleted file mode 100644 index 6b482f85..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt +++ /dev/null @@ -1,479 +0,0 @@ -/* - * 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 mastodon.account - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.infrastructure.exposedquery.FollowerQueryServiceImpl -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.MediaType -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-user2.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class AccountApiTest { - - @Autowired - private lateinit var followerQueryServiceImpl: FollowerQueryServiceImpl - - @Autowired - private lateinit var actorRepository: ActorRepository - - - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(springSecurity()) - .build() - } - - @Test - fun `apiV1AccountsVerifyCredentialsGetにreadでアクセスできる`() { - mockMvc - .get("/api/v1/accounts/verify_credentials") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsVerifyCredentialsGetにread_accountsでアクセスできる`() { - mockMvc - .get("/api/v1/accounts/verify_credentials") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:accounts"))) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - } - - @Test - @WithAnonymousUser - fun apiV1AccountsVerifyCredentialsGetに匿名でアクセスすると401() { - mockMvc - .get("/api/v1/accounts/verify_credentials") - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithAnonymousUser - fun apiV1AccountsPostに匿名でPOSTしたらアカウントを作成できる() = runTest { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("username", "api-test-user-1") - param("password", "very-secure-password") - param("email", "test@example.com") - param("agreement", "true") - param("locale", "") - with(jwt()) - with(csrf()) - } - .asyncDispatch() - .andExpect { status { isFound() } } - - actorRepository.findByNameAndDomain("api-test-user-1", "example.com") - } - - @Test - @WithAnonymousUser - fun apiV1AccountsPostで必須パラメーター以外を省略しても作成できる() = runTest { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("username", "api-test-user-2") - param("password", "very-secure-password") - with(jwt()) - with(csrf()) - } - .asyncDispatch() - .andExpect { status { isFound() } } - - actorRepository.findByNameAndDomain("api-test-user-2", "example.com") - } - - @Test - @WithAnonymousUser - fun apiV1AccountsPostでusernameパラメーターを省略したら400() = runTest { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("password", "api-test-user-3") - with(csrf()) - with(jwt()) - } - .andDo { print() } - .andExpect { status { isUnprocessableEntity() } } - } - - @Test - @WithAnonymousUser - fun apiV1AccountsPostでpasswordパラメーターを省略したら400() = runTest { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("username", "api-test-user-4") - with(csrf()) - with(jwt()) - } - .andExpect { status { isUnprocessableEntity() } } - } - - @Test - @Disabled("JSONでも作れるようにするため") - @WithAnonymousUser - fun apiV1AccountsPostでJSONで作ろうとしても400() { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_JSON - content = """{"username":"api-test-user-5","password":"very-very-secure-password"}""" - with(csrf()) - } - .andExpect { status { isUnsupportedMediaType() } } - } - - @Test - @WithAnonymousUser - fun apiV1AccountsPostにCSRFトークンは必要() { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("username", "api-test-user-2") - param("password", "very-secure-password") - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdGet 匿名でアカウント情報を取得できる`() { - mockMvc - .get("/api/v1/accounts/1") - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdFollowPost write_follows権限でPOSTでフォローできる`() { - mockMvc - .post("/api/v1/accounts/2/follow") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:follows"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdFollowPost write権限でPOSTでフォローできる`() { - mockMvc - .post("/api/v1/accounts/2/follow") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdFollowPost read権限でだと403`() { - mockMvc - .post("/api/v1/accounts/2/follow") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AAccountsIdFollowPost 匿名だと401`() { - mockMvc - .post("/api/v1/accounts/2/follow") { - contentType = MediaType.APPLICATION_JSON - with(csrf()) - } - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AAccountsIdFollowPost 匿名の場合通常csrfトークンは持ってないので403`() { - mockMvc - .post("/api/v1/accounts/2/follow") { - contentType = MediaType.APPLICATION_JSON - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1AccountsRelationshipsGet 匿名だと401`() { - mockMvc - .get("/api/v1/accounts/relationships") - .andExpect { status { isUnauthorized() } } - } - - @Test - fun `apiV1AccountsRelationshipsGet read_follows権限を持っていたら取得できる`() { - mockMvc - .get("/api/v1/accounts/relationships") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:follows"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsRelationshipsGet read権限を持っていたら取得できる`() { - mockMvc - .get("/api/v1/accounts/relationships") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsRelationshipsGet write権限だと403`() { - mockMvc - .get("/api/v1/accounts/relationships") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .andExpect { status { isForbidden() } } - } - - @Test - @Sql("/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql") - fun `apiV1AccountsIdFollowPost フォローできる`() = runTest { - mockMvc - .post("/api/v1/accounts/3733363/follow") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "37335363") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - - val alreadyFollow = followerQueryServiceImpl.alreadyFollow(3733363, 37335363) - - assertThat(alreadyFollow).isTrue() - } - - @Test - fun `apiV1AccountsIdMutePost write権限でミュートできる`() { - mockMvc - .post("/api/v1/accounts/2/mute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdMutePost write_mutes権限でミュートできる`() { - mockMvc - .post("/api/v1/accounts/2/mute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:mutes"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdMutePost read権限だと403`() = runTest { - mockMvc - .post("/api/v1/accounts/2/mute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdMutePost 匿名だと401`() = runTest { - mockMvc - .post("/api/v1/accounts/2/mute") { - contentType = MediaType.APPLICATION_JSON - with(csrf()) - } - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdMutePost csrfトークンがないと403`() = runTest { - mockMvc - .post("/api/v1/accounts/2/mute") { - contentType = MediaType.APPLICATION_JSON - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1AccountsIdUnmutePost write権限でアンミュートできる`() { - mockMvc - .post("/api/v1/accounts/2/unmute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdUnmutePost write_mutes権限でアンミュートできる`() { - mockMvc - .post("/api/v1/accounts/2/unmute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:mutes"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1AccountsIdUnmutePost read権限だと403`() = runTest { - mockMvc - .post("/api/v1/accounts/2/unmute") { - contentType = MediaType.APPLICATION_JSON - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdUnmutePost 匿名だと401`() = runTest { - mockMvc - .post("/api/v1/accounts/2/unmute") { - contentType = MediaType.APPLICATION_JSON - with(csrf()) - } - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdUnmutePost csrfトークンがないと403`() = runTest { - mockMvc - .post("/api/v1/accounts/2/unmute") { - contentType = MediaType.APPLICATION_JSON - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1MutesGet read権限でミュートしているアカウント一覧を取得できる`() { - mockMvc - .get("/api/v1/mutes") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1MutesGet read_mutes権限でミュートしているアカウント一覧を取得できる`() { - mockMvc - .get("/api/v1/mutes") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:mutes"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1MutesGet write権限だと403`() { - mockMvc - .get("/api/v1/mutes") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun `apiV1MutesGet 匿名だと401`() { - mockMvc - .get("/api/v1/mutes") - .andExpect { status { isUnauthorized() } } - } - - @Test - fun `apiV1AccountsIdStatusesGet read権限で取得できる`() { - mockMvc - .get("/api/v1/accounts/1/statuses") - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - @WithAnonymousUser - fun `apiV1AccountsIdStatusesGet 匿名でもpublic投稿を取得できる`() { - mockMvc - .get("/api/v1/accounts/1/statuses") - .asyncDispatch() - .andExpect { status { isOk() } } - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/mastodon/apps/AppTest.kt b/hideout-core/src/intTest/kotlin/mastodon/apps/AppTest.kt deleted file mode 100644 index 8ce170eb..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/apps/AppTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 mastodon.apps - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThat -import org.flywaydb.core.Flyway -import org.jetbrains.exposed.sql.selectAll -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.MediaType -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -class AppTest { - - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - @Test - @WithAnonymousUser - fun apiV1AppsPostにformで匿名でappを作成できる() { - mockMvc - .post("/api/v1/apps") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("client_name", "test-client") - param("redirect_uris", "https://example.com") - param("scopes", "write read") - param("website", "https://example.com") - } - .asyncDispatch() - .andExpect { status { isOk() } } - - - val app = RegisteredClient - .selectAll().where { RegisteredClient.clientName eq "test-client" } - .single() - - assertThat(app[RegisteredClient.clientName]).isEqualTo("test-client") - assertThat(app[RegisteredClient.redirectUris]).isEqualTo("https://example.com") - assertThat(app[RegisteredClient.scopes]).isEqualTo("read,write") - } - - @Test - @WithAnonymousUser - fun apiV1AppsPostにjsonで匿名でappを作成できる() { - mockMvc - .post("/api/v1/apps") { - contentType = MediaType.APPLICATION_JSON - content = """{ - "client_name": "test-client-2", - "redirect_uris": "https://example.com", - "scopes": "write read", - "website": "https;//example.com" -}""" - } - .asyncDispatch() - .andExpect { status { isOk() } } - - val app = RegisteredClient - .selectAll().where { RegisteredClient.clientName eq "test-client-2" } - .single() - - assertThat(app[RegisteredClient.clientName]).isEqualTo("test-client-2") - assertThat(app[RegisteredClient.redirectUris]).isEqualTo("https://example.com") - assertThat(app[RegisteredClient.scopes]).isEqualTo("read,write") - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/mastodon/filter/FilterTest.kt b/hideout-core/src/intTest/kotlin/mastodon/filter/FilterTest.kt deleted file mode 100644 index bb3dccae..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/filter/FilterTest.kt +++ /dev/null @@ -1,715 +0,0 @@ -/* - * 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 mastodon.filter - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.domain.mastodon.model.generated.FilterKeywordsPostRequest -import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequest -import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequestKeyword -import dev.usbharu.hideout.domain.mastodon.model.generated.V1FilterPostRequest -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.MediaType -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.delete -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", "/sql/filter/test-filter.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class FilterTest { - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - @Test - fun `apiV2FiltersPost write権限で追加できる`() { - mockMvc - .post("/api/v2/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterPostRequest( - title = "mute test", - context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), - filterAction = FilterPostRequest.FilterAction.warn, - expiresIn = null, - keywordsAttributes = listOf( - FilterPostRequestKeyword( - keyword = "hoge", - wholeWord = false, - regex = true - ) - ) - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { - content { - jsonPath("$.keywords[0].keyword") { - value("hoge") - } - } - } - } - - @Test - fun `apiV2FiltersPost write_filters権限で追加できる`() { - mockMvc - .post("/api/v2/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterPostRequest( - title = "mute test", - context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), - filterAction = FilterPostRequest.FilterAction.warn, - expiresIn = null, - keywordsAttributes = listOf( - FilterPostRequestKeyword( - keyword = "fuga", - wholeWord = true, - regex = false - ) - ) - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { - content { - jsonPath("$.keywords[0].keyword") { - value("fuga") - } - } - } - } - - @Test - fun `apiV2FiltersPost read権限で401`() { - mockMvc - .post("/api/v2/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterPostRequest( - title = "mute test", - context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), - filterAction = FilterPostRequest.FilterAction.warn, - expiresIn = null, - keywordsAttributes = listOf( - FilterPostRequestKeyword( - keyword = "fuga", - wholeWord = true, - regex = false - ) - ) - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersGet read権限で取得できる`() { - mockMvc - .get("/api/v2/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersGet read_filters権限で取得できる`() { - mockMvc - .get("/api/v2/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersGet write権限で401`() { - mockMvc - .get("/api/v2/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersIdGet read権限で取得できる`() { - mockMvc - .get("/api/v2/filters/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - - @Test - fun `apiV2FiltersIdGet read_filters権限で取得できる`() { - mockMvc - .get("/api/v2/filters/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersIdGet write権限で401`() { - mockMvc - .get("/api/v2/filters/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsGet read権限で取得できる`() { - mockMvc - .get("/api/v2/filters/1/keywords") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsGet read_filters権限で取得できる`() { - mockMvc - .get("/api/v2/filters/1/keywords") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsGet writeで403`() { - mockMvc - .get("/api/v2/filters/1/keywords") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsPost writeで追加できる`() { - mockMvc - .post("/api/v2/filters/1/keywords") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterKeywordsPostRequest( - "hage", false, false - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsPost write_filtersで追加できる`() { - mockMvc - .post("/api/v2/filters/1/keywords") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterKeywordsPostRequest( - "hage", false, false - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdKeywordsPost readで403`() { - mockMvc - .post("/api/v2/filters/1/keywords") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - FilterKeywordsPostRequest( - "hage", false, false - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersKeywordsIdGet readで取得できる`() { - mockMvc - .get("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersKeywordsIdGet read_filtersで取得できる`() { - mockMvc - .get("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersKeywordsIdGet writeだと403`() { - mockMvc - .get("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersKeyowrdsIdDelete writeで削除できる`() = runTest { - mockMvc - .delete("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersKeyowrdsIdDelete write_filtersで削除できる`() = runTest { - mockMvc - .delete("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersKeyowrdsIdDelete readで403`() = runTest { - mockMvc - .delete("/api/v2/filters/keywords/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersFilterIdStatuses readで取得できる`() { - mockMvc - .get("/api/v2/filters/1/statuses") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdStatuses read_filtersで取得できる`() { - mockMvc - .get("/api/v2/filters/1/statuses") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersFilterIdStatuses writeで403`() { - mockMvc - .get("/api/v2/filters/1/statuses") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersStatusesIdGet readで取得できる`() { - mockMvc - .get("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersStatusesIdGet read_filtersで取得できる`() { - mockMvc - .get("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersStatusesIdGet writeで403`() { - mockMvc - .get("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV2FiltersStatusesIdDelete writeで削除できる`() { - mockMvc - .delete("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersStatusesIdDelete write_filtersで削除できる`() { - mockMvc - .delete("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV2FiltersStatusesIdDelete readで403`() { - mockMvc - .delete("/api/v2/filters/statuses/1") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1FiltersGet readで取得できる`() { - mockMvc - .get("/api/v1/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersGet read_filtersで取得できる`() { - mockMvc - .get("/api/v1/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersGet writeで403`() { - mockMvc - .get("/api/v1/filters") { - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1FiltersPost writeで新規作成`() { - mockMvc - .post("/api/v1/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - V1FilterPostRequest( - phrase = "hoge", - context = listOf(V1FilterPostRequest.Context.home), - irreversible = false, - wholeWord = false, - expiresIn = null - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersPost write_filtersで新規作成`() { - mockMvc - .post("/api/v1/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - V1FilterPostRequest( - phrase = "hoge", - context = listOf(V1FilterPostRequest.Context.home), - irreversible = false, - wholeWord = false, - expiresIn = null - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersPost readで403`() { - mockMvc - .post("/api/v1/filters") { - contentType = MediaType.APPLICATION_JSON - content = ActivityPubConfig().objectMapper().writeValueAsString( - V1FilterPostRequest( - phrase = "hoge", - context = listOf(V1FilterPostRequest.Context.home), - irreversible = false, - wholeWord = false, - expiresIn = null - ) - ) - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1FiltersIdGet readで取得できる`() { - mockMvc - .get("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersIdGet read_filtersで取得できる`() { - mockMvc - .get("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersIdGet writeで403`() { - mockMvc - .get("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - fun `apiV1FiltersIdDelete writeで削除できる`() { - mockMvc - .delete("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersIdDelete write_filtersで削除できる`() { - mockMvc - .delete("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1FiltersIdDelete readで403`() { - mockMvc - .delete("/api/v1/filters/1") { - with( - jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} \ No newline at end of file diff --git a/hideout-core/src/intTest/kotlin/mastodon/media/MediaTest.kt b/hideout-core/src/intTest/kotlin/mastodon/media/MediaTest.kt deleted file mode 100644 index 77f23281..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/media/MediaTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 mastodon.media - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.core.service.media.MediaDataStore -import dev.usbharu.hideout.core.service.media.MediaSaveRequest -import dev.usbharu.hideout.core.service.media.SuccessSavedMedia -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.mock.web.MockMultipartFile -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.multipart -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class MediaTest { - - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - - @MockBean - private lateinit var mediaDataStore: MediaDataStore - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - @Test - fun メディアをアップロードできる() = runTest { - whenever(mediaDataStore.save(any())).doReturn(SuccessSavedMedia("", "a", "a")) - - mockMvc - .multipart("/api/v1/media") { - - file( - MockMultipartFile( - "file", - "400x400.png", - "image/png", - String.javaClass.classLoader.getResourceAsStream("media/400x400.png") - ) - ) - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun write_mediaスコープでメディアをアップロードできる() = runTest { - whenever(mediaDataStore.save(any())).doReturn(SuccessSavedMedia("", "b", "b")) - - mockMvc - .multipart("/api/v1/media") { - - file( - MockMultipartFile( - "file", - "400x400.png", - "image/png", - String.javaClass.classLoader.getResourceAsStream("media/400x400.png") - ) - ) - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:media"))) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun 権限がないと403() = runTest { - whenever(mediaDataStore.save(any())).doReturn(SuccessSavedMedia("", "", "")) - - mockMvc - .multipart("/api/v1/media") { - - file( - MockMultipartFile( - "file", - "400x400.png", - "image/png", - String.javaClass.classLoader.getResourceAsStream("media/400x400.png") - ) - ) - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) - } - .andExpect { status { isForbidden() } } - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } - -} diff --git a/hideout-core/src/intTest/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt b/hideout-core/src/intTest/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt deleted file mode 100644 index 7405caf6..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 mastodon.notifications - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class], properties = ["hideout.use-mongodb=false"]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-user2.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/notification/test-notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/notification/test-mastodon_notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class ExposedNotificationsApiPaginationTest { - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @Test - fun `通知を取得できる`() = runTest { - val content = mockMvc - .get("/api/v1/notifications") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("65") - assertThat(value.last().id).isEqualTo("26") - } - - @Test - fun maxIdを指定して通知を取得できる() = runTest { - val content = mockMvc - .get("/api/v1/notifications?max_id=26") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("25") - assertThat(value.last().id).isEqualTo("1") - - } - - @Test - fun minIdを指定して通知を取得できる() = runTest { - val content = mockMvc - .get("/api/v1/notifications?min_id=25") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value.first().id).isEqualTo("65") - assertThat(value.last().id).isEqualTo("26") - } - - @Test - fun 結果が0件のときはページネーションのヘッダーがない() = runTest { - val content = mockMvc - .get("/api/v1/notifications?max_id=1") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - doesNotExist("Link") - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - assertThat(value).size().isZero() - } - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} \ No newline at end of file diff --git a/hideout-core/src/intTest/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt b/hideout-core/src/intTest/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt deleted file mode 100644 index 0de15332..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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 mastodon.notifications - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import dev.usbharu.hideout.mastodon.infrastructure.mongorepository.MongoMastodonNotificationRepository -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext -import java.time.Instant - -@SpringBootTest(classes = [SpringApplication::class], properties = ["hideout.use-mongodb=true"]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-user2.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/notification/test-notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class MongodbNotificationsApiPaginationTest { - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @Test - fun `通知を取得できる`() = runTest { - val content = mockMvc - .get("/api/v1/notifications") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andDo { print() } - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - Assertions.assertThat(value.first().id).isEqualTo("65") - Assertions.assertThat(value.last().id).isEqualTo("26") - } - - @Test - fun maxIdを指定して通知を取得できる() = runTest { - val content = mockMvc - .get("/api/v1/notifications?max_id=26") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - Assertions.assertThat(value.first().id).isEqualTo("25") - Assertions.assertThat(value.last().id).isEqualTo("1") - - } - - @Test - fun minIdを指定して通知を取得できる() = runTest { - val content = mockMvc - .get("/api/v1/notifications?min_id=25") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - string( - "Link", - "; rel=\"next\", ; rel=\"prev\"" - ) - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - Assertions.assertThat(value.first().id).isEqualTo("65") - Assertions.assertThat(value.last().id).isEqualTo("26") - } - - @Test - fun 結果が0件のときはページネーションのヘッダーがない() = runTest { - val content = mockMvc - .get("/api/v1/notifications?max_id=1") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { - header { - doesNotExist("Link") - } - } - .andReturn() - .response - .contentAsString - - val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) - - Assertions.assertThat(value).size().isZero() - } - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - companion object { - @JvmStatic - @BeforeAll - fun setupMongodb( - @Autowired mongoMastodonNotificationRepository: MongoMastodonNotificationRepository, - ) { - - mongoMastodonNotificationRepository.deleteAll() - - val notifications = (1..65).map { - MastodonNotification( - it.toLong(), - 1, - NotificationType.follow, - Instant.now(), - 2, - null, - null, - null - ) - } - - mongoMastodonNotificationRepository.saveAll(notifications) - } - - @JvmStatic - @AfterAll - fun dropDatabase( - @Autowired flyway: Flyway, - @Autowired mongodbMastodonNotificationRepository: MongoMastodonNotificationRepository, - @Autowired owlProducer: OwlProducer, - ) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - mongodbMastodonNotificationRepository.deleteAll() - } - } -} \ No newline at end of file diff --git a/hideout-core/src/intTest/kotlin/mastodon/status/StatusTest.kt b/hideout-core/src/intTest/kotlin/mastodon/status/StatusTest.kt deleted file mode 100644 index 817d5dfc..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/status/StatusTest.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * 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 mastodon.status - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions -import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThat -import org.flywaydb.core.Flyway -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.MediaType -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.put -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext -import java.time.Instant - -@SpringBootTest(classes = [SpringApplication::class]) -@AutoConfigureMockMvc -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-post.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -@Sql("/sql/test-custom-emoji.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class StatusTest { - - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - @Test - fun 投稿できる() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = """{"status":"hello"}""" - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun write_statusesスコープで投稿できる() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = """{"status":"hello"}""" - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:statuses")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun 権限がないと403() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = """{"status":"hello"}""" - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .andExpect { status { isForbidden() } } - } - - @Test - @WithAnonymousUser - fun 匿名だと401() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = """{"status":"hello"}""" - with(csrf()) - } - .andExpect { status { isUnauthorized() } } - } - - @Test - @WithAnonymousUser - fun 匿名の場合通常はcsrfが無いので403() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = """{"status":"hello"}""" - } - .andExpect { status { isForbidden() } } - } - - @Test - fun formでも投稿できる() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("status", "hello") - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:statuses")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun in_reply_to_idを指定したら返信として処理される() { - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - //language=JSON - content = """{ - "status": "hello", - "in_reply_to_id": "1" -}""" - with( - jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) - ) - } - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { jsonPath("\$.in_reply_to_id") { value("1") } } - } - - @Test - fun ユニコード絵文字をリアクションできる() { - mockMvc - .put("/api/v1/statuses/1/emoji_reactions/😭") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .andDo { print() } - .asyncDispatch() - .andExpect { status { isOk() } } - - val reaction = - Reactions.selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() - assertThat(reaction.emoji).isEqualTo(UnicodeEmoji("😭")) - assertThat(reaction.postId).isEqualTo(1) - assertThat(reaction.actorId).isEqualTo(1) - } - - @Test - fun 存在しない絵文字はフォールバックされる() { - mockMvc - .put("/api/v1/statuses/1/emoji_reactions/hoge") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .andDo { print() } - .asyncDispatch() - .andExpect { status { isOk() } } - - val reaction = - Reactions.selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() - assertThat(reaction.emoji).isEqualTo(UnicodeEmoji("❤")) - assertThat(reaction.postId).isEqualTo(1) - assertThat(reaction.actorId).isEqualTo(1) - } - - @Test - fun カスタム絵文字をリアクションできる() { - mockMvc - .put("/api/v1/statuses/1/emoji_reactions/kotlin") { - with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) - } - .andDo { print() } - .asyncDispatch() - .andExpect { status { isOk() } } - - val reaction = - Reactions.leftJoin(CustomEmojis).selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) } - .single() - .toReaction() - assertThat(reaction.emoji).isEqualTo( - CustomEmoji( - 1, - "kotlin", - "example.com", - null, - "https://example.com/emojis/kotlin", - null, - Instant.ofEpochMilli(1704700290036) - ) - ) - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt b/hideout-core/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt deleted file mode 100644 index 21719e85..00000000 --- a/hideout-core/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 mastodon.timelines - -import dev.usbharu.hideout.SpringApplication -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.flywaydb.core.Flyway -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.test.context.support.WithAnonymousUser -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors -import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers -import org.springframework.test.context.jdbc.Sql -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.context.WebApplicationContext - -@SpringBootTest(classes = [SpringApplication::class]) -@Transactional -@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) -class TimelineApiTest { - @Autowired - private lateinit var context: WebApplicationContext - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun beforeEach() { - mockMvc = MockMvcBuilders.webAppContextSetup(context) - .apply(SecurityMockMvcConfigurers.springSecurity()) - .build() - } - - @Test - fun `apiV1TimelinesHomeGetにreadでアクセスできる`() { - mockMvc - .get("/api/v1/timelines/home") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1TimelinesHomeGetにread statusesでアクセスできる`() { - mockMvc - .get("/api/v1/timelines/home") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - @WithAnonymousUser - fun apiV1TimelineHomeGetに匿名でアクセスすると401() { - mockMvc - .get("/api/v1/timelines/home") - .andExpect { status { isUnauthorized() } } - } - - @Test - fun apiV1TimelinesPublicGetにreadでアクセスできる() { - mockMvc - .get("/api/v1/timelines/public") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - fun `apiV1TimelinesPublicGetにread statusesでアクセスできる`() { - mockMvc - .get("/api/v1/timelines/public") { - with( - SecurityMockMvcRequestPostProcessors.jwt() - .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) - ) - } - .asyncDispatch() - .andExpect { status { isOk() } } - } - - @Test - @WithAnonymousUser - fun apiV1TimeinesPublicGetに匿名でアクセスできる() { - mockMvc - .get("/api/v1/timelines/public") - .asyncDispatch() - .andExpect { status { isOk() } } - } - - companion object { - @JvmStatic - @AfterAll - fun dropDatabase(@Autowired flyway: Flyway, @Autowired owlProducer: OwlProducer) { - flyway.clean() - flyway.migrate() - runBlocking { - owlProducer.stop() - } - } - } -} diff --git a/hideout-core/src/intTest/kotlin/util/TestTransaction.kt b/hideout-core/src/intTest/kotlin/util/TestTransaction.kt deleted file mode 100644 index 72c3b42b..00000000 --- a/hideout-core/src/intTest/kotlin/util/TestTransaction.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 util - -import dev.usbharu.hideout.application.external.Transaction - -object TestTransaction : Transaction { - override suspend fun transaction(block: suspend () -> T): T = block() - - override suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T = block() -} diff --git a/hideout-core/src/intTest/kotlin/util/WithHttpSignature.kt b/hideout-core/src/intTest/kotlin/util/WithHttpSignature.kt deleted file mode 100644 index 64fd643f..00000000 --- a/hideout-core/src/intTest/kotlin/util/WithHttpSignature.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 util - -import org.springframework.core.annotation.AliasFor -import org.springframework.security.test.context.support.TestExecutionEvent -import org.springframework.security.test.context.support.WithSecurityContext -import java.lang.annotation.Inherited - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) -@Retention(AnnotationRetention.RUNTIME) -@Inherited -@MustBeDocumented -@WithSecurityContext(factory = WithHttpSignatureSecurityContextFactory::class) -annotation class WithHttpSignature( - @get:AliasFor( - annotation = WithSecurityContext::class - ) val setupBefore: TestExecutionEvent = TestExecutionEvent.TEST_METHOD, - val keyId: String = "https://example.com/users/test-user#pubkey", - val url: String = "https://example.com/inbox", - val method: String = "GET" -) diff --git a/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt b/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt deleted file mode 100644 index 6d809267..00000000 --- a/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 util - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import kotlinx.coroutines.runBlocking -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.test.context.support.WithSecurityContextFactory -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import java.net.URL - -class WithHttpSignatureSecurityContextFactory( - private val actorRepository: ActorRepository, - private val transaction: Transaction -) : WithSecurityContextFactory { - - private val securityContextStrategy = SecurityContextHolder.getContextHolderStrategy() - - override fun createSecurityContext(annotation: WithHttpSignature): SecurityContext = runBlocking { - val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken( - annotation.keyId, HttpRequest( - URL("https://example.com/inbox"), - HttpHeaders(mapOf()), HttpMethod.GET - ) - ) - val httpSignatureUser = transaction.transaction { - val findByKeyId = - actorRepository.findByKeyId(annotation.keyId) ?: throw UserNotFoundException.withKeyId(annotation.keyId) - HttpSignatureUser( - findByKeyId.name, - findByKeyId.domain, - findByKeyId.id, - true, - true, - mutableListOf() - ) - } - preAuthenticatedAuthenticationToken.details = httpSignatureUser - preAuthenticatedAuthenticationToken.isAuthenticated = true - val emptyContext = securityContextStrategy.createEmptyContext() - emptyContext.authentication = preAuthenticatedAuthenticationToken - return@runBlocking emptyContext - } - -} diff --git a/hideout-core/src/intTest/kotlin/util/WithMockHttpSignature.kt b/hideout-core/src/intTest/kotlin/util/WithMockHttpSignature.kt deleted file mode 100644 index 86392316..00000000 --- a/hideout-core/src/intTest/kotlin/util/WithMockHttpSignature.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 util - -import org.springframework.core.annotation.AliasFor -import org.springframework.security.test.context.support.TestExecutionEvent -import org.springframework.security.test.context.support.WithSecurityContext -import java.lang.annotation.Inherited - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) -@Retention(AnnotationRetention.RUNTIME) -@Inherited -@MustBeDocumented -@WithSecurityContext(factory = WithMockHttpSignatureSecurityContextFactory::class) -annotation class WithMockHttpSignature( - @get:AliasFor( - annotation = WithSecurityContext::class - ) val setupBefore: TestExecutionEvent = TestExecutionEvent.TEST_METHOD, - val username: String = "test-user", - val domain: String = "example.com", - val keyId: String = "https://example.com/users/test-user#pubkey", - val id: Long = 1234L, - val url: String = "https://example.com/inbox", - val method: String = "GET" -) diff --git a/hideout-core/src/intTest/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt b/hideout-core/src/intTest/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt deleted file mode 100644 index 1114bdcd..00000000 --- a/hideout-core/src/intTest/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 util - -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.test.context.support.WithSecurityContextFactory -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import java.net.URL - -class WithMockHttpSignatureSecurityContextFactory : - WithSecurityContextFactory { - - private val securityContextStrategy = SecurityContextHolder.getContextHolderStrategy() - - override fun createSecurityContext(annotation: WithMockHttpSignature): SecurityContext { - val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken( - annotation.keyId, HttpRequest( - URL(annotation.url), - HttpHeaders(mapOf()), HttpMethod.valueOf(annotation.method.uppercase()) - ) - ) - val httpSignatureUser = HttpSignatureUser( - annotation.username, - annotation.domain, - annotation.id, - true, - true, - mutableListOf() - ) - preAuthenticatedAuthenticationToken.details = httpSignatureUser - preAuthenticatedAuthenticationToken.isAuthenticated = true - val emptyContext = securityContextStrategy.createEmptyContext() - emptyContext.authentication = preAuthenticatedAuthenticationToken - return emptyContext - } -} diff --git a/hideout-core/src/intTest/resources/application.yml b/hideout-core/src/intTest/resources/application.yml deleted file mode 100644 index 57ab70fa..00000000 --- a/hideout-core/src/intTest/resources/application.yml +++ /dev/null @@ -1,40 +0,0 @@ -hideout: - debug: - trace-query-exception: true - trace-query-call: true - url: "https://example.com" - use-mongodb: true - security: - jwt: - generate: true - key-id: a - private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" - public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" - storage: - type: local - private: false - -spring: - flyway: - enabled: true - clean-disabled: false - datasource: - driver-class-name: org.h2.Driver - url: "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;" - username: "" - password: - data: - mongodb: - auto-index-creation: true - host: localhost - port: 27017 - database: hideout-integration-test - h2: - console: - enabled: true - -# exposed: -# generate-ddl: true -# excluded-packages: dev.usbharu.hideout.core.infrastructure.kjobexposed -server: - port: 8080 diff --git a/hideout-core/src/intTest/resources/junit-platform.properties b/hideout-core/src/intTest/resources/junit-platform.properties deleted file mode 100644 index acfa9e5a..00000000 --- a/hideout-core/src/intTest/resources/junit-platform.properties +++ /dev/null @@ -1,2 +0,0 @@ -junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$Random -junit.jupiter.testmethod.order.default=org.junit.jupiter.api.MethodOrderer$Random diff --git a/hideout-core/src/intTest/resources/logback.xml b/hideout-core/src/intTest/resources/logback.xml deleted file mode 100644 index a8bb21c4..00000000 --- a/hideout-core/src/intTest/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n - - - - - - - diff --git a/hideout-core/src/intTest/resources/media/400x400.png b/hideout-core/src/intTest/resources/media/400x400.png deleted file mode 100644 index 0d2e71be..00000000 Binary files a/hideout-core/src/intTest/resources/media/400x400.png and /dev/null differ diff --git a/hideout-core/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql b/hideout-core/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql deleted file mode 100644 index a2b01c22..00000000 --- a/hideout-core/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql +++ /dev/null @@ -1,17 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (3733363, 'follow-test-user-1', 'example.com', 'follow-test-user-1-name', '', - 'https://example.com/users/follow-test-user-1/inbox', - 'https://example.com/users/follow-test-user-1/outbox', 'https://example.com/users/follow-test-user-1', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/follow-test-user-1#pubkey', 'https://example.com/users/follow-test-user-1/following', - 'https://example.com/users/follow-test-user-1/followers', 0, false, 0, 0, 0, null), - (37335363, 'follow-test-user-2', 'example.com', 'follow-test-user-2-name', '', - 'https://example.com/users/follow-test-user-2/inbox', - 'https://example.com/users/follow-test-user-2/outbox', 'https://example.com/users/follow-test-user-2', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/follow-test-user-2#pubkey', 'https://example.com/users/follow-test-user-2/following', - 'https://example.com/users/follow-test-user-2/followers', 0, false, 0, 0, 0, null); diff --git a/hideout-core/src/intTest/resources/sql/accounts/test-accounts-statuses.sql b/hideout-core/src/intTest/resources/sql/accounts/test-accounts-statuses.sql deleted file mode 100644 index 10352e07..00000000 --- a/hideout-core/src/intTest/resources/sql/accounts/test-accounts-statuses.sql +++ /dev/null @@ -1,202 +0,0 @@ -insert into posts (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, deleted) -VALUES (1, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/1', - null, null, false, 'https://example.com/users/1/posts/1', false), - (2, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/2', - null, 1, false, 'https://example.com/users/1/posts/2', false), - (3, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/3', - null, null, false, 'https://example.com/users/1/posts/3', false), - (4, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/4', - null, 3, false, 'https://example.com/users/1/posts/4', false), - (5, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/5', - null, null, false, 'https://example.com/users/1/posts/5', false), - (6, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/6', - null, null, false, 'https://example.com/users/1/posts/6', false), - (7, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/7', - null, null, false, 'https://example.com/users/1/posts/7', false), - (8, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/8', - null, 7, false, 'https://example.com/users/1/posts/8', false), - (9, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/9', - null, null, false, 'https://example.com/users/1/posts/9', false), - (10, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/10', - null, 9, false, 'https://example.com/users/1/posts/10', false), - (11, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/11', - null, null, false, 'https://example.com/users/1/posts/11', false), - (12, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/12', - null, null, false, 'https://example.com/users/1/posts/12', false), - (13, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/13', - null, null, false, 'https://example.com/users/1/posts/13', false), - (14, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/14', - null, 13, false, 'https://example.com/users/1/posts/14', false), - (15, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/15', - null, null, false, 'https://example.com/users/1/posts/15', false), - (16, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/16', - null, 15, false, 'https://example.com/users/1/posts/16', false), - (17, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/17', - null, null, false, 'https://example.com/users/1/posts/17', false), - (18, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/18', - null, null, false, 'https://example.com/users/1/posts/18', false), - (19, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/19', - null, null, false, 'https://example.com/users/1/posts/19', false), - (20, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/20', - null, 19, false, 'https://example.com/users/1/posts/20', false), - (21, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/21', - null, null, false, 'https://example.com/users/1/posts/21', false), - (22, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/22', - null, 21, false, 'https://example.com/users/1/posts/22', false), - (23, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/23', - null, null, false, 'https://example.com/users/1/posts/23', false), - (24, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/24', - null, null, false, 'https://example.com/users/1/posts/24', false), - (25, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/25', - null, null, false, 'https://example.com/users/1/posts/25', false), - (26, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/26', - null, 25, false, 'https://example.com/users/1/posts/26', false), - (27, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/27', - null, null, false, 'https://example.com/users/1/posts/27', false), - (28, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/28', - null, 27, false, 'https://example.com/users/1/posts/28', false), - (29, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/29', - null, null, false, 'https://example.com/users/1/posts/29', false), - (30, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/30', - null, null, false, 'https://example.com/users/1/posts/30', false), - (31, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/31', - null, null, false, 'https://example.com/users/1/posts/31', false), - (32, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/32', - null, 31, false, 'https://example.com/users/1/posts/32', false), - (33, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/33', - null, null, false, 'https://example.com/users/1/posts/33', false), - (34, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/34', - null, 33, false, 'https://example.com/users/1/posts/34', false), - (35, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/35', - null, null, false, 'https://example.com/users/1/posts/35', false), - (36, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/36', - null, null, false, 'https://example.com/users/1/posts/36', false), - (37, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/37', - null, null, false, 'https://example.com/users/1/posts/37', false), - (38, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/38', - null, 37, false, 'https://example.com/users/1/posts/38', false), - (39, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/39', - null, null, false, 'https://example.com/users/1/posts/39', false), - (40, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/40', - null, 39, false, 'https://example.com/users/1/posts/40', false), - (41, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/41', - null, null, false, 'https://example.com/users/1/posts/41', false), - (42, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/42', - null, null, false, 'https://example.com/users/1/posts/42', false), - (43, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/43', - null, null, false, 'https://example.com/users/1/posts/43', false), - (44, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/44', - null, 43, false, 'https://example.com/users/1/posts/44', false), - (45, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/45', - null, null, false, 'https://example.com/users/1/posts/45', false), - (46, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/46', - null, 45, false, 'https://example.com/users/1/posts/46', false), - (47, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/47', - null, null, false, 'https://example.com/users/1/posts/47', false), - (48, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/48', - null, null, false, 'https://example.com/users/1/posts/48', false), - (49, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/49', - null, null, false, 'https://example.com/users/1/posts/49', false), - (50, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/50', - null, 49, false, 'https://example.com/users/1/posts/50', false), - (51, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/51', - null, null, false, 'https://example.com/users/1/posts/51', false), - (52, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/52', - null, 51, false, 'https://example.com/users/1/posts/52', false), - (53, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/53', - null, null, false, 'https://example.com/users/1/posts/53', false), - (54, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/54', - null, null, false, 'https://example.com/users/1/posts/54', false), - (55, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/55', - null, null, false, 'https://example.com/users/1/posts/55', false), - (56, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/56', - null, 55, false, 'https://example.com/users/1/posts/56', false), - (57, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/57', - null, null, false, 'https://example.com/users/1/posts/57', false), - (58, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/58', - null, 57, false, 'https://example.com/users/1/posts/58', false), - (59, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/59', - null, null, false, 'https://example.com/users/1/posts/59', false), - (60, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/60', - null, null, false, 'https://example.com/users/1/posts/60', false), - (61, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/61', - null, null, false, 'https://example.com/users/1/posts/61', false), - (62, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/62', - null, 61, false, 'https://example.com/users/1/posts/62', false), - (63, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/63', - null, null, false, 'https://example.com/users/1/posts/63', false), - (64, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/64', - null, 63, false, 'https://example.com/users/1/posts/64', false), - (65, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/65', - null, null, false, 'https://example.com/users/1/posts/65', false), - (66, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/66', - null, null, false, 'https://example.com/users/1/posts/66', false), - (67, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/67', - null, null, false, 'https://example.com/users/1/posts/67', false), - (68, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/68', - null, 67, false, 'https://example.com/users/1/posts/68', false), - (69, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/69', - null, null, false, 'https://example.com/users/1/posts/69', false), - (70, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/70', - null, 69, false, 'https://example.com/users/1/posts/70', false), - (71, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/71', - null, null, false, 'https://example.com/users/1/posts/71', false), - (72, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/72', - null, null, false, 'https://example.com/users/1/posts/72', false), - (73, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/73', - null, null, false, 'https://example.com/users/1/posts/73', false), - (74, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/74', - null, 73, false, 'https://example.com/users/1/posts/74', false), - (75, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/75', - null, null, false, 'https://example.com/users/1/posts/75', false), - (76, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/76', - null, 75, false, 'https://example.com/users/1/posts/76', false), - (77, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/77', - null, null, false, 'https://example.com/users/1/posts/77', false), - (78, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/78', - null, null, false, 'https://example.com/users/1/posts/78', false), - (79, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/79', - null, null, false, 'https://example.com/users/1/posts/79', false), - (80, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/80', - null, 79, false, 'https://example.com/users/1/posts/80', false), - (81, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/81', - null, null, false, 'https://example.com/users/1/posts/81', false), - (82, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/82', - null, 81, false, 'https://example.com/users/1/posts/82', false), - (83, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/83', - null, null, false, 'https://example.com/users/1/posts/83', false), - (84, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/84', - null, null, false, 'https://example.com/users/1/posts/84', false), - (85, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/85', - null, null, false, 'https://example.com/users/1/posts/85', false), - (86, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/86', - null, 85, false, 'https://example.com/users/1/posts/86', false), - (87, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/87', - null, null, false, 'https://example.com/users/1/posts/87', false), - (88, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/88', - null, 87, false, 'https://example.com/users/1/posts/88', false), - (89, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/89', - null, null, false, 'https://example.com/users/1/posts/89', false), - (90, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/90', - null, null, false, 'https://example.com/users/1/posts/90', false), - (91, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/91', - null, null, false, 'https://example.com/users/1/posts/91', false), - (92, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/92', - null, 91, false, 'https://example.com/users/1/posts/92', false), - (93, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/93', - null, null, false, 'https://example.com/users/1/posts/93', false), - (94, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/94', - null, 93, false, 'https://example.com/users/1/posts/94', false), - (95, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/95', - null, null, false, 'https://example.com/users/1/posts/95', false), - (96, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/96', - null, null, false, 'https://example.com/users/1/posts/96', false), - (97, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/97', - null, null, false, 'https://example.com/users/1/posts/97', false), - (98, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/98', - null, 97, false, 'https://example.com/users/1/posts/98', false), - (99, 1, null, '

this is test

', 'this is test', 1706684146436, 2, 'https://example.com/users/1/posts/99', - null, null, false, 'https://example.com/users/1/posts/99', false), - (100, 1, null, '

this is test

', 'this is test', 1706684146436, 0, 'https://example.com/users/1/posts/100', - null, 99, false, 'https://example.com/users/1/posts/100', false); \ No newline at end of file diff --git a/hideout-core/src/intTest/resources/sql/filter/test-filter.sql b/hideout-core/src/intTest/resources/sql/filter/test-filter.sql deleted file mode 100644 index d06d6bc0..00000000 --- a/hideout-core/src/intTest/resources/sql/filter/test-filter.sql +++ /dev/null @@ -1,4 +0,0 @@ -insert into filters (id, user_id, name, context, action) -VALUES (1, 1, 'test filter', 'home', 'warn'); -insert into filter_keywords(id, filter_id, keyword, mode) -VALUES (1, 1, 'hoge', 'NONE') \ No newline at end of file diff --git a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql b/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql deleted file mode 100644 index dc2ab9f1..00000000 --- a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql +++ /dev/null @@ -1,28 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (8, 'test-user8', 'example.com', 'Im test-user8.', 'THis account is test-user8.', - 'https://example.com/users/test-user8/inbox', - 'https://example.com/users/test-user8/outbox', 'https://example.com/users/test-user8', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user8#pubkey', 'https://example.com/users/test-user8/following', - 'https://example.com/users/test-user8/followers', 0, false, 0, 0, 0, null), - (9, 'test-user9', 'follower.example.com', 'Im test-user9.', 'THis account is test-user9.', - 'https://follower.example.com/users/test-user9/inbox', - 'https://follower.example.com/users/test-user9/outbox', 'https://follower.example.com/users/test-user9', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - null, 12345678, - 'https://follower.example.com/users/test-user9#pubkey', - 'https://follower.example.com/users/test-user9/following', - 'https://follower.example.com/users/test-user9/followers', 0, false, 0, 0, 0, null); - -insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, - ignore_follow_request) -VALUES (9, 8, true, false, false, false, false); - -insert into POSTS (ID, ACTOR_ID, OVERVIEW, CONTENT, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE, - AP_ID) -VALUES (1239, 8, null, '

test post

', 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239', - null, null, false, - 'https://example.com/users/test-user8/posts/1239'); diff --git a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql b/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql deleted file mode 100644 index a44861f4..00000000 --- a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql +++ /dev/null @@ -1,29 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (4, 'test-user4', 'example.com', 'Im test user4.', 'THis account is test user4.', - 'https://example.com/users/test-user4/inbox', - 'https://example.com/users/test-user4/outbox', 'https://example.com/users/test-user4', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user4#pubkey', 'https://example.com/users/test-user4/following', - 'https://example.com/users/test-user4/followers', 0, false, 0, 0, 0, null), - (5, 'test-user5', 'follower.example.com', 'Im test user5.', 'THis account is test user5.', - 'https://follower.example.com/users/test-user5/inbox', - 'https://follower.example.com/users/test-user5/outbox', 'https://follower.example.com/users/test-user5', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - null, 12345678, - 'https://follower.example.com/users/test-user5#pubkey', - 'https://follower.example.com/users/test-user5/following', - 'https://follower.example.com/users/test-user5/followers', 0, false, 0, 0, 0, null); - -insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, - ignore_follow_request) -VALUES (5, 4, true, false, false, false, false); - -insert into POSTS (ID, "actor_id", OVERVIEW, CONTENT, TEXT, "created_at", VISIBILITY, URL, "repost_id", "reply_id", - SENSITIVE, - AP_ID) -VALUES (1237, 4, null, '

test post

', 'test post', 12345680, 0, 'https://example.com/users/test-user4/posts/1237', - null, null, false, - 'https://example.com/users/test-user4/posts/1237'); diff --git a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql b/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql deleted file mode 100644 index 77cb3395..00000000 --- a/hideout-core/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql +++ /dev/null @@ -1,29 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (6, 'test-user6', 'example.com', 'Im test-user6.', 'THis account is test-user6.', - 'https://example.com/users/test-user6/inbox', - 'https://example.com/users/test-user6/outbox', 'https://example.com/users/test-user6', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user6#pubkey', 'https://example.com/users/test-user6/following', - 'https://example.com/users/test-user6/followers', 0, false, 0, 0, 0, null), - (7, 'test-user7', 'follower.example.com', 'Im test-user7.', 'THis account is test-user7.', - 'https://follower.example.com/users/test-user7/inbox', - 'https://follower.example.com/users/test-user7/outbox', 'https://follower.example.com/users/test-user7', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - null, 12345678, - 'https://follower.example.com/users/test-user7#pubkey', - 'https://follower.example.com/users/test-user7/following', - 'https://follower.example.com/users/test-user7/followers', 0, false, 0, 0, 0, null); - -insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, - ignore_follow_request) -VALUES (7, 6, true, false, false, false, false); - -insert into POSTS (ID, "actor_ID", OVERVIEW, CONTENT, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", - SENSITIVE, - AP_ID) -VALUES (1238, 6, null, '

test post

', 'test post', 12345680, 1, 'https://example.com/users/test-user6/posts/1238', - null, null, false, - 'https://example.com/users/test-user6/posts/1238'); diff --git a/hideout-core/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql b/hideout-core/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql deleted file mode 100644 index 6cc9db8c..00000000 --- a/hideout-core/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql +++ /dev/null @@ -1,25 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (11, 'test-user11', 'example.com', 'Im test-user11.', 'THis account is test-user11.', - 'https://example.com/users/test-user11/inbox', - 'https://example.com/users/test-user11/outbox', 'https://example.com/users/test-user11', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user11#pubkey', 'https://example.com/users/test-user11/following', - 'https://example.com/users/test-user11/followers', 0, false, 0, 0, 0, null); - -insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, - deleted) -VALUES (1242, 11, null, '

test post

', 'test post', 12345680, 0, - 'https://example.com/users/test-user11/posts/1242', null, null, false, - 'https://example.com/users/test-user11/posts/1242', false); - -insert into MEDIA (ID, NAME, URL, REMOTE_URL, THUMBNAIL_URL, TYPE, BLURHASH, MIME_TYPE, DESCRIPTION) -VALUES (1, 'test-media', 'https://example.com/media/test-media.png', null, null, 0, null, 'image/png', null), - (2, 'test-media2', 'https://example.com/media/test-media2.png', null, null, 0, null, 'image/png', null); - -insert into POSTS_MEDIA(POST_ID, MEDIA_ID) -VALUES (1242, 1), - (1242, 2); diff --git a/hideout-core/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql b/hideout-core/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql deleted file mode 100644 index 41ac73a4..00000000 --- a/hideout-core/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql +++ /dev/null @@ -1,20 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (10, 'test-user10', 'example.com', 'Im test-user10.', 'THis account is test-user10.', - 'https://example.com/users/test-user10/inbox', - 'https://example.com/users/test-user10/outbox', 'https://example.com/users/test-user10', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user10#pubkey', 'https://example.com/users/test-user10/following', - 'https://example.com/users/test-user10/followers', 0, false, 0, 0, 0, null); - -insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, - deleted) -VALUES (1240, 10, null, '

test post

', 'test post', 12345680, 0, - 'https://example.com/users/test-user10/posts/1240', null, null, false, - 'https://example.com/users/test-user10/posts/1240', false), - (1241, 10, null, '

test post

', 'test post', 12345680, 0, - 'https://example.com/users/test-user10/posts/1241', null, 1240, false, - 'https://example.com/users/test-user10/posts/1241', false); diff --git a/hideout-core/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql b/hideout-core/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql deleted file mode 100644 index 250cfb5a..00000000 --- a/hideout-core/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql +++ /dev/null @@ -1,17 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (3, 'test-user3', 'example.com', 'Im test user3.', 'THis account is test user3.', - 'https://example.com/users/test-user3/inbox', - 'https://example.com/users/test-user3/outbox', 'https://example.com/users/test-user3', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user3#pubkey', 'https://example.com/users/test-user3/following', - 'https://example.com/users/test-user3/followers', 0, false, 0, 0, 0, null); - -insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, - deleted) -VALUES (1236, 3, null, '

test post

', 'test post', 12345680, 2, 'https://example.com/users/test-user3/posts/1236', - null, null, false, - 'https://example.com/users/test-user3/posts/1236', false) diff --git a/hideout-core/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql b/hideout-core/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql deleted file mode 100644 index 777f9244..00000000 --- a/hideout-core/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql +++ /dev/null @@ -1,17 +0,0 @@ -insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, - key_id, following, followers, instance, locked, following_count, followers_count, posts_count, - last_post_at) -VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', - 'https://example.com/users/test-user/inbox', - 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', - 'https://example.com/users/test-users/followers', 0, false, 0, 0, 0, null); - -insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, - deleted) -VALUES (1234, 1, null, '

test post

', 'test post', 12345680, 0, 'https://example.com/users/test-user/posts/1234', - null, null, false, - 'https://example.com/users/test-user/posts/1234', false) diff --git a/hideout-core/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql b/hideout-core/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql deleted file mode 100644 index b132734d..00000000 --- a/hideout-core/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql +++ /dev/null @@ -1,17 +0,0 @@ -insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, - key_id, following, followers, instance, locked, following_count, followers_count, posts_count, - last_post_at) -VALUES (2, 'test-user2', 'example.com', 'Im test user2.', 'THis account is test user2.', - 'https://example.com/users/test-user2/inbox', - 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', - 'https://example.com/users/test-user2/followers', 0, false, 0, 0, 0, null); - -insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id, - deleted) -VALUES (1235, 2, null, '

test post

', 'test post', 12345680, 1, 'https://example.com/users/test-user2/posts/1235', - null, null, false, - 'https://example.com/users/test-user2/posts/1235', false) diff --git a/hideout-core/src/intTest/resources/sql/notification/test-mastodon_notifications.sql b/hideout-core/src/intTest/resources/sql/notification/test-mastodon_notifications.sql deleted file mode 100644 index c97a25a7..00000000 --- a/hideout-core/src/intTest/resources/sql/notification/test-mastodon_notifications.sql +++ /dev/null @@ -1,66 +0,0 @@ -insert into mastodon_notifications (id, user_id, type, created_at, account_id, status_id, report_id, relationship_serverance_event_id) -values (1, 1, 'follow', current_timestamp, 2, null, null, null), - (2, 1, 'follow', current_timestamp, 2, null, null, null), - (3, 1, 'follow', current_timestamp, 2, null, null, null), - (4, 1, 'follow', current_timestamp, 2, null, null, null), - (5, 1, 'follow', current_timestamp, 2, null, null, null), - (6, 1, 'follow', current_timestamp, 2, null, null, null), - (7, 1, 'follow', current_timestamp, 2, null, null, null), - (8, 1, 'follow', current_timestamp, 2, null, null, null), - (9, 1, 'follow', current_timestamp, 2, null, null, null), - (10, 1, 'follow', current_timestamp, 2, null, null, null), - (11, 1, 'follow', current_timestamp, 2, null, null, null), - (12, 1, 'follow', current_timestamp, 2, null, null, null), - (13, 1, 'follow', current_timestamp, 2, null, null, null), - (14, 1, 'follow', current_timestamp, 2, null, null, null), - (15, 1, 'follow', current_timestamp, 2, null, null, null), - (16, 1, 'follow', current_timestamp, 2, null, null, null), - (17, 1, 'follow', current_timestamp, 2, null, null, null), - (18, 1, 'follow', current_timestamp, 2, null, null, null), - (19, 1, 'follow', current_timestamp, 2, null, null, null), - (20, 1, 'follow', current_timestamp, 2, null, null, null), - (21, 1, 'follow', current_timestamp, 2, null, null, null), - (22, 1, 'follow', current_timestamp, 2, null, null, null), - (23, 1, 'follow', current_timestamp, 2, null, null, null), - (24, 1, 'follow', current_timestamp, 2, null, null, null), - (25, 1, 'follow', current_timestamp, 2, null, null, null), - (26, 1, 'follow', current_timestamp, 2, null, null, null), - (27, 1, 'follow', current_timestamp, 2, null, null, null), - (28, 1, 'follow', current_timestamp, 2, null, null, null), - (29, 1, 'follow', current_timestamp, 2, null, null, null), - (30, 1, 'follow', current_timestamp, 2, null, null, null), - (31, 1, 'follow', current_timestamp, 2, null, null, null), - (32, 1, 'follow', current_timestamp, 2, null, null, null), - (33, 1, 'follow', current_timestamp, 2, null, null, null), - (34, 1, 'follow', current_timestamp, 2, null, null, null), - (35, 1, 'follow', current_timestamp, 2, null, null, null), - (36, 1, 'follow', current_timestamp, 2, null, null, null), - (37, 1, 'follow', current_timestamp, 2, null, null, null), - (38, 1, 'follow', current_timestamp, 2, null, null, null), - (39, 1, 'follow', current_timestamp, 2, null, null, null), - (40, 1, 'follow', current_timestamp, 2, null, null, null), - (41, 1, 'follow', current_timestamp, 2, null, null, null), - (42, 1, 'follow', current_timestamp, 2, null, null, null), - (43, 1, 'follow', current_timestamp, 2, null, null, null), - (44, 1, 'follow', current_timestamp, 2, null, null, null), - (45, 1, 'follow', current_timestamp, 2, null, null, null), - (46, 1, 'follow', current_timestamp, 2, null, null, null), - (47, 1, 'follow', current_timestamp, 2, null, null, null), - (48, 1, 'follow', current_timestamp, 2, null, null, null), - (49, 1, 'follow', current_timestamp, 2, null, null, null), - (50, 1, 'follow', current_timestamp, 2, null, null, null), - (51, 1, 'follow', current_timestamp, 2, null, null, null), - (52, 1, 'follow', current_timestamp, 2, null, null, null), - (53, 1, 'follow', current_timestamp, 2, null, null, null), - (54, 1, 'follow', current_timestamp, 2, null, null, null), - (55, 1, 'follow', current_timestamp, 2, null, null, null), - (56, 1, 'follow', current_timestamp, 2, null, null, null), - (57, 1, 'follow', current_timestamp, 2, null, null, null), - (58, 1, 'follow', current_timestamp, 2, null, null, null), - (59, 1, 'follow', current_timestamp, 2, null, null, null), - (60, 1, 'follow', current_timestamp, 2, null, null, null), - (61, 1, 'follow', current_timestamp, 2, null, null, null), - (62, 1, 'follow', current_timestamp, 2, null, null, null), - (63, 1, 'follow', current_timestamp, 2, null, null, null), - (64, 1, 'follow', current_timestamp, 2, null, null, null), - (65, 1, 'follow', current_timestamp, 2, null, null, null); \ No newline at end of file diff --git a/hideout-core/src/intTest/resources/sql/notification/test-notifications.sql b/hideout-core/src/intTest/resources/sql/notification/test-notifications.sql deleted file mode 100644 index 38982603..00000000 --- a/hideout-core/src/intTest/resources/sql/notification/test-notifications.sql +++ /dev/null @@ -1,66 +0,0 @@ -insert into notifications(id, type, user_id, source_actor_id, post_id, text, reaction_id, created_at) -VALUES (1, 'follow', 1, 2, null, null, null, current_timestamp), - (2, 'follow', 1, 2, null, null, null, current_timestamp), - (3, 'follow', 1, 2, null, null, null, current_timestamp), - (4, 'follow', 1, 2, null, null, null, current_timestamp), - (5, 'follow', 1, 2, null, null, null, current_timestamp), - (6, 'follow', 1, 2, null, null, null, current_timestamp), - (7, 'follow', 1, 2, null, null, null, current_timestamp), - (8, 'follow', 1, 2, null, null, null, current_timestamp), - (9, 'follow', 1, 2, null, null, null, current_timestamp), - (10, 'follow', 1, 2, null, null, null, current_timestamp), - (11, 'follow', 1, 2, null, null, null, current_timestamp), - (12, 'follow', 1, 2, null, null, null, current_timestamp), - (13, 'follow', 1, 2, null, null, null, current_timestamp), - (14, 'follow', 1, 2, null, null, null, current_timestamp), - (15, 'follow', 1, 2, null, null, null, current_timestamp), - (16, 'follow', 1, 2, null, null, null, current_timestamp), - (17, 'follow', 1, 2, null, null, null, current_timestamp), - (18, 'follow', 1, 2, null, null, null, current_timestamp), - (19, 'follow', 1, 2, null, null, null, current_timestamp), - (20, 'follow', 1, 2, null, null, null, current_timestamp), - (21, 'follow', 1, 2, null, null, null, current_timestamp), - (22, 'follow', 1, 2, null, null, null, current_timestamp), - (23, 'follow', 1, 2, null, null, null, current_timestamp), - (24, 'follow', 1, 2, null, null, null, current_timestamp), - (25, 'follow', 1, 2, null, null, null, current_timestamp), - (26, 'follow', 1, 2, null, null, null, current_timestamp), - (27, 'follow', 1, 2, null, null, null, current_timestamp), - (28, 'follow', 1, 2, null, null, null, current_timestamp), - (29, 'follow', 1, 2, null, null, null, current_timestamp), - (30, 'follow', 1, 2, null, null, null, current_timestamp), - (31, 'follow', 1, 2, null, null, null, current_timestamp), - (32, 'follow', 1, 2, null, null, null, current_timestamp), - (33, 'follow', 1, 2, null, null, null, current_timestamp), - (34, 'follow', 1, 2, null, null, null, current_timestamp), - (35, 'follow', 1, 2, null, null, null, current_timestamp), - (36, 'follow', 1, 2, null, null, null, current_timestamp), - (37, 'follow', 1, 2, null, null, null, current_timestamp), - (38, 'follow', 1, 2, null, null, null, current_timestamp), - (39, 'follow', 1, 2, null, null, null, current_timestamp), - (40, 'follow', 1, 2, null, null, null, current_timestamp), - (41, 'follow', 1, 2, null, null, null, current_timestamp), - (42, 'follow', 1, 2, null, null, null, current_timestamp), - (43, 'follow', 1, 2, null, null, null, current_timestamp), - (44, 'follow', 1, 2, null, null, null, current_timestamp), - (45, 'follow', 1, 2, null, null, null, current_timestamp), - (46, 'follow', 1, 2, null, null, null, current_timestamp), - (47, 'follow', 1, 2, null, null, null, current_timestamp), - (48, 'follow', 1, 2, null, null, null, current_timestamp), - (49, 'follow', 1, 2, null, null, null, current_timestamp), - (50, 'follow', 1, 2, null, null, null, current_timestamp), - (51, 'follow', 1, 2, null, null, null, current_timestamp), - (52, 'follow', 1, 2, null, null, null, current_timestamp), - (53, 'follow', 1, 2, null, null, null, current_timestamp), - (54, 'follow', 1, 2, null, null, null, current_timestamp), - (55, 'follow', 1, 2, null, null, null, current_timestamp), - (56, 'follow', 1, 2, null, null, null, current_timestamp), - (57, 'follow', 1, 2, null, null, null, current_timestamp), - (58, 'follow', 1, 2, null, null, null, current_timestamp), - (59, 'follow', 1, 2, null, null, null, current_timestamp), - (60, 'follow', 1, 2, null, null, null, current_timestamp), - (61, 'follow', 1, 2, null, null, null, current_timestamp), - (62, 'follow', 1, 2, null, null, null, current_timestamp), - (63, 'follow', 1, 2, null, null, null, current_timestamp), - (64, 'follow', 1, 2, null, null, null, current_timestamp), - (65, 'follow', 1, 2, null, null, null, current_timestamp); \ No newline at end of file diff --git a/hideout-core/src/intTest/resources/sql/test-custom-emoji.sql b/hideout-core/src/intTest/resources/sql/test-custom-emoji.sql deleted file mode 100644 index 83c2747a..00000000 --- a/hideout-core/src/intTest/resources/sql/test-custom-emoji.sql +++ /dev/null @@ -1,3 +0,0 @@ -insert into emojis(id, name, domain, instance_id, url, category, created_at) -VALUES (1, 'kotlin', 'example.com', null, 'https://example.com/emojis/kotlin', null, - TIMESTAMP '2024-01-08 07:51:30.036Z'); diff --git a/hideout-core/src/intTest/resources/sql/test-post.sql b/hideout-core/src/intTest/resources/sql/test-post.sql deleted file mode 100644 index cd623188..00000000 --- a/hideout-core/src/intTest/resources/sql/test-post.sql +++ /dev/null @@ -1,4 +0,0 @@ -insert into posts (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, - ap_id) -VALUES (1, 1, null, '

test post

', 'hello', 1234455, 0, 'https://localhost/users/1/posts/1', null, null, false, - 'https://users/1/posts/1'); diff --git a/hideout-core/src/intTest/resources/sql/test-user.sql b/hideout-core/src/intTest/resources/sql/test-user.sql deleted file mode 100644 index d46e5280..00000000 --- a/hideout-core/src/intTest/resources/sql/test-user.sql +++ /dev/null @@ -1,10 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', - 'https://example.com/users/test-user/inbox', - 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', - 'https://example.com/users/test-users/followers', 0, false, 0, 0, 0, null); diff --git a/hideout-core/src/intTest/resources/sql/test-user2.sql b/hideout-core/src/intTest/resources/sql/test-user2.sql deleted file mode 100644 index 0f736704..00000000 --- a/hideout-core/src/intTest/resources/sql/test-user2.sql +++ /dev/null @@ -1,10 +0,0 @@ -insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance, locked, following_count, followers_count, - posts_count, last_post_at) -VALUES (2, 'test-user2', 'example.com', 'Im test user.', 'THis account is test user.', - 'https://example.com/users/test-user2/inbox', - 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', - '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', - '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, - 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', - 'https://example.com/users/test-user2s/followers', 0, false, 0, 0, 0, null); diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt deleted file mode 100644 index 6c19c683..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.activitypub.domain - -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject - -object Constant { - val context = listOf( - StringOrObject("https://www.w3.org/ns/activitystreams"), - StringOrObject("https://w3id.org/security/v1"), - StringOrObject( - mapOf( - "manuallyApprovesFollowers" to "as:manuallyApprovesFollowers", - "sensitive" to "as:sensitive", - "Hashtag" to "as:Hashtag", - "quoteUrl" to "as:quoteUrl", - "toot" to "http://joinmastodon.org/ns#", - "Emoji" to "toot:Emoji", - "featured" to "toot:featured", - "discoverable" to "toot:discoverable", - "schema" to "http://schema.org#", - "PropertyValue" to "schema:PropertyValue", - "value" to "schema:value", - ) - ) - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt deleted file mode 100644 index 8ce12a19..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.activitypub.domain.exception - -import java.io.Serial - -class ActivityPubProcessException : 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 = 5370068873167636639L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt deleted file mode 100644 index d3125395..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.activitypub.domain.exception - -import java.io.Serial - -class FailedProcessException : 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 = -1305337651143409144L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt deleted file mode 100644 index 528277a8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.activitypub.domain.exception - -import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException -import java.io.Serial - -class FailedToGetActivityPubResourceException : FailedToGetResourcesException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 6420233106776818052L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt deleted file mode 100644 index aa50d3db..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.activitypub.domain.exception - -import java.io.Serial - -class HttpSignatureUnauthorizedException : 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 = -6449793151674654501L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt deleted file mode 100644 index 84c090cc..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Accept @JsonCreator constructor( - type: List = emptyList(), - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object, - override val actor: String -) : Object( - type = add(type, "Accept") -), - HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Accept - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - return result - } - - override fun toString(): String { - return "Accept(" + - "apObject=$apObject, " + - "actor='$actor'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt deleted file mode 100644 index c07eab7c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Announce @JsonCreator constructor( - type: List = emptyList(), - @JsonProperty("object") - val apObject: String, - override val actor: String, - override val id: String, - val published: String, - val to: List = emptyList(), - val cc: List = emptyList() -) : Object( - type = add(type, "Announce") -), - HasActor, - HasId { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Announce - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - if (published != other.published) return false - if (to != other.to) return false - if (cc != other.cc) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - return result - } - - override fun toString(): String { - return "Announce(" + - "apObject='$apObject', " + - "actor='$actor', " + - "id='$id', " + - "published='$published', " + - "to=$to, " + - "cc=$cc" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt deleted file mode 100644 index 53e12cd5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Create( - type: List = emptyList(), - val name: String? = null, - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object, - override val actor: String, - override val id: String, - val to: List = emptyList(), - val cc: List = emptyList() -) : Object( - type = add(type, "Create") -), - HasId, - HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Create - - if (name != other.name) return false - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - if (to != other.to) return false - if (cc != other.cc) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (name?.hashCode() ?: 0) - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - return result - } - - override fun toString(): String { - return "Create(" + - "name=$name, " + - "apObject=$apObject, " + - "actor='$actor', " + - "id='$id', " + - "to=$to, " + - "cc=$cc" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt deleted file mode 100644 index 43ec1a51..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Delete : Object, HasId, HasActor { - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object - val published: String - override var actor: String = "" - override var id: String = "" - - constructor( - type: List = emptyList(), - actor: String, - id: String, - `object`: Object, - published: String - ) : super(add(type, "Delete")) { - this.apObject = `object` - this.published = published - this.actor = actor - this.id = id - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Delete - - if (apObject != other.apObject) return false - if (published != other.published) return false - if (actor != other.actor) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String { - return "Delete(" + - "apObject=$apObject, " + - "published='$published', " + - "actor='$actor', " + - "id='$id'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt deleted file mode 100644 index 632fb7cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Document( - type: List = emptyList(), - @JsonSetter(nulls = Nulls.AS_EMPTY) - override val name: String = "", - val mediaType: String, - val url: String -) : Object( - type = add(type, "Document") -), - HasName { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Document - - if (mediaType != other.mediaType) return false - if (url != other.url) return false - if (name != other.name) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + mediaType.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + name.hashCode() - return result - } - - override fun toString(): String = "Document(mediaType=$mediaType, url=$url, name='$name') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt deleted file mode 100644 index f1f7ae7b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Emoji( - type: List, - override val name: String, - override val id: String, - val updated: String, - val icon: Image -) : Object( - type = add(type, "Emoji") -), - HasName, - HasId { - - override fun toString(): String { - return "Emoji(" + - "name='$name', " + - "id='$id', " + - "updated='$updated', " + - "icon=$icon" + - ")" + - " ${super.toString()}" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Emoji - - if (name != other.name) return false - if (id != other.id) return false - if (updated != other.updated) return false - if (icon != other.icon) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + updated.hashCode() - result = 31 * result + icon.hashCode() - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt deleted file mode 100644 index f404946a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Follow( - type: List = emptyList(), - @JsonProperty("object") val apObject: String, - override val actor: String, - val id: String? = null -) : Object( - type = add(type, "Follow") -), - HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Follow - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + (id?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Follow(" + - "apObject='$apObject', " + - "actor='$actor', " + - "id=$id" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt deleted file mode 100644 index a522574c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Image( - type: List = emptyList(), - val mediaType: String? = null, - val url: String -) : Object( - add(type, "Image") -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Image - - if (mediaType != other.mediaType) return false - if (url != other.url) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (mediaType?.hashCode() ?: 0) - result = 31 * result + url.hashCode() - return result - } - - override fun toString(): String { - return "Image(" + - "mediaType=$mediaType, " + - "url='$url'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt deleted file mode 100644 index 88a8e48d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize - -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) -open class JsonLd { - @JsonProperty("@context") - @JsonDeserialize(contentUsing = StringOrObjectDeserializer::class) - @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class) - @JsonInclude(JsonInclude.Include.NON_EMPTY) - var context: List = emptyList() - set(value) { - field = value.filterNot { it.isEmpty() } - } - - @JsonCreator - constructor(context: List?) { - if (context != null) { - this.context = context.filterNotNull().filterNot { it.isEmpty() } - } else { - this.context = emptyList() - } - } - - protected constructor() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is JsonLd) return false - - return context == other.context - } - - override fun hashCode(): Int = context.hashCode() - - override fun toString(): String = "JsonLd(context=$context)" -} - -class ContextDeserializer : JsonDeserializer() { - - override fun deserialize( - p0: com.fasterxml.jackson.core.JsonParser?, - p1: com.fasterxml.jackson.databind.DeserializationContext? - ): String { - val readTree: JsonNode = p0?.codec?.readTree(p0) ?: return "" - if (readTree.isValueNode) { - return readTree.textValue() - } - return "" - } -} - -class ContextSerializer : JsonSerializer>() { - - @Deprecated("Deprecated in Java") - override fun isEmpty(value: List?): Boolean = value.isNullOrEmpty() - - override fun isEmpty(provider: SerializerProvider?, value: List?): Boolean = value.isNullOrEmpty() - - override fun serialize(value: List?, gen: JsonGenerator?, serializers: SerializerProvider) { - if (value.isNullOrEmpty()) { - serializers.defaultSerializeNull(gen) - return - } - if (value.size == 1) { - serializers.findValueSerializer(StringOrObject::class.java).serialize(value[0], gen, serializers) - } else { - gen?.writeStartArray() - value.forEach { - serializers.findValueSerializer(StringOrObject::class.java).serialize(it, gen, serializers) - } - gen?.writeEndArray() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt deleted file mode 100644 index bb046d56..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Key( - override val id: String, - val owner: String, - val publicKeyPem: String -) : Object( - type = add(list = emptyList(), type = "Key") -), - HasId { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Key - - if (owner != other.owner) return false - if (publicKeyPem != other.publicKeyPem) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + owner.hashCode() - result = 31 * result + publicKeyPem.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String = "Key(owner=$owner, publicKeyPem=$publicKeyPem, id='$id') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt deleted file mode 100644 index 6b46cb54..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Like( - type: List = emptyList(), - override val actor: String, - override val id: String, - @JsonProperty("object") val apObject: String, - val content: String, - @JsonDeserialize(contentUsing = ObjectDeserializer::class) val tag: List = emptyList() -) : Object( - type = add(type, "Like") -), - HasId, - HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Like - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - if (content != other.content) return false - if (tag != other.tag) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + content.hashCode() - result = 31 * result + tag.hashCode() - return result - } - - override fun toString(): String { - return "Like(" + - "actor='$actor', " + - "id='$id', " + - "apObject='$apObject', " + - "content='$content', " + - "tag=$tag" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt deleted file mode 100644 index b925c861..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Note -@Suppress("LongParameterList", "CyclomaticComplexMethod") -constructor( - type: List = emptyList(), - override val id: String, - val attributedTo: String, - val content: String, - val published: String, - val to: List = emptyList(), - val cc: List = emptyList(), - val sensitive: Boolean = false, - val inReplyTo: String? = null, - val attachment: List = emptyList(), - @JsonDeserialize(contentUsing = ObjectDeserializer::class) - val tag: List = emptyList(), - val quoteUri: String? = null, - val quoteUrl: String? = null, - @JsonProperty("_misskey_quote") - val misskeyQuote: String? = null -) : Object( - type = add(type, "Note") -), - HasId { - - @Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod") - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Note - - if (id != other.id) return false - if (attributedTo != other.attributedTo) return false - if (content != other.content) return false - if (published != other.published) return false - if (to != other.to) return false - if (cc != other.cc) return false - if (sensitive != other.sensitive) return false - if (inReplyTo != other.inReplyTo) return false - if (attachment != other.attachment) return false - if (tag != other.tag) return false - if (quoteUri != other.quoteUri) return false - if (quoteUrl != other.quoteUrl) return false - if (misskeyQuote != other.misskeyQuote) return false - - return true - } - - @Suppress("CyclomaticComplexMethod") - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + attributedTo.hashCode() - result = 31 * result + content.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - result = 31 * result + sensitive.hashCode() - result = 31 * result + (inReplyTo?.hashCode() ?: 0) - result = 31 * result + attachment.hashCode() - result = 31 * result + tag.hashCode() - result = 31 * result + (quoteUri?.hashCode() ?: 0) - result = 31 * result + (quoteUrl?.hashCode() ?: 0) - result = 31 * result + (misskeyQuote?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Note(" + - "id='$id', " + - "attributedTo='$attributedTo', " + - "content='$content', " + - "published='$published', " + - "to=$to, " + - "cc=$cc, " + - "sensitive=$sensitive, " + - "inReplyTo=$inReplyTo, " + - "attachment=$attachment, " + - "tag=$tag, " + - "quoteUri=$quoteUri, " + - "quoteUrl=$quoteUrl, " + - "misskeyQuote=$misskeyQuote" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt deleted file mode 100644 index 3f4a7dbe..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Person -@Suppress("LongParameterList") -constructor( - type: List = emptyList(), - val name: String?, - override val id: String, - var preferredUsername: String, - var summary: String?, - var inbox: String, - var outbox: String, - var url: String, - private var icon: Image?, - var publicKey: Key, - var endpoints: Map = emptyMap(), - var followers: String?, - var following: String?, - val manuallyApprovesFollowers: Boolean? = false -) : Object(add(type, "Person")), HasId { - - @Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod") - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Person - - if (name != other.name) return false - if (id != other.id) return false - if (preferredUsername != other.preferredUsername) return false - if (summary != other.summary) return false - if (inbox != other.inbox) return false - if (outbox != other.outbox) return false - if (url != other.url) return false - if (icon != other.icon) return false - if (publicKey != other.publicKey) return false - if (endpoints != other.endpoints) return false - if (followers != other.followers) return false - if (following != other.following) return false - if (manuallyApprovesFollowers != other.manuallyApprovesFollowers) return false - - return true - } - - @Suppress("CyclomaticComplexMethod") - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (name?.hashCode() ?: 0) - result = 31 * result + id.hashCode() - result = 31 * result + preferredUsername.hashCode() - result = 31 * result + (summary?.hashCode() ?: 0) - result = 31 * result + inbox.hashCode() - result = 31 * result + outbox.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + (icon?.hashCode() ?: 0) - result = 31 * result + publicKey.hashCode() - result = 31 * result + endpoints.hashCode() - result = 31 * result + (followers?.hashCode() ?: 0) - result = 31 * result + (following?.hashCode() ?: 0) - result = 31 * result + (manuallyApprovesFollowers?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Person(" + - "name=$name, " + - "id='$id', " + - "preferredUsername='$preferredUsername', " + - "summary=$summary, " + - "inbox='$inbox', " + - "outbox='$outbox', " + - "url='$url', " + - "icon=$icon, " + - "publicKey=$publicKey, " + - "endpoints=$endpoints, " + - "followers=$followers, " + - "following=$following, " + - "manuallyApprovesFollowers=$manuallyApprovesFollowers" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt deleted file mode 100644 index 5216c2a1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Reject( - override val actor: String, - override val id: String, - @JsonDeserialize(using = ObjectDeserializer::class) @JsonProperty("object") val apObject: Object -) : Object(listOf("Reject")), HasId, HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Reject - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - return result - } - - override fun toString(): String { - return "Reject(" + - "actor='$actor', " + - "id='$id', " + - "apObject=$apObject" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt deleted file mode 100644 index 3a9969db..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt +++ /dev/null @@ -1,76 +0,0 @@ -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.* - -open class StringOrObject { - var contextString: String? = null - var contextObject: Map? = null - - @JsonCreator - protected constructor() - - constructor(string: String) : this() { - contextString = string - } - - constructor(contextObject: Map) : this() { - this.contextObject = contextObject - } - - fun isEmpty(): Boolean = contextString.isNullOrEmpty() and contextObject.isNullOrEmpty() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as StringOrObject - - if (contextString != other.contextString) return false - if (contextObject != other.contextObject) return false - - return true - } - - override fun hashCode(): Int { - var result = contextString?.hashCode() ?: 0 - result = 31 * result + (contextObject?.hashCode() ?: 0) - return result - } - - override fun toString(): String = "StringOrObject(contextString=$contextString, contextObject=$contextObject)" -} - -class StringOrObjectDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): StringOrObject { - val readTree: JsonNode = p?.codec?.readTree(p) ?: return StringOrObject("") - return if (readTree.isValueNode) { - StringOrObject(readTree.textValue()) - } else if (readTree.isObject) { - val map: Map = ctxt.readTreeAsValue( - readTree, - ctxt.typeFactory.constructType(object : TypeReference>() {}) - ) - StringOrObject(map) - } else { - StringOrObject("") - } - } -} - -class StringORObjectSerializer : JsonSerializer() { - override fun serialize(value: StringOrObject?, gen: JsonGenerator?, serializers: SerializerProvider) { - if (value == null) { - serializers.defaultSerializeNull(gen) - return - } - if (value.contextString != null) { - gen?.writeString(value.contextString) - } else { - serializers.defaultSerializeValue(value.contextObject, gen) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt deleted file mode 100644 index 56ea7cde..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Tombstone(type: List = emptyList(), override val id: String) : - Object(add(type, "Tombstone")), - HasId { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Tombstone - - return id == other.id - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String = "Tombstone(id='$id') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt deleted file mode 100644 index 038d4054..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Undo( - type: List = emptyList(), - override val actor: String, - override val id: String, - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") val apObject: Object, - val published: String? -) : Object(add(type, "Undo")), HasId, HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Undo - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - if (published != other.published) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + (published?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Undo(" + - "actor='$actor', " + - "id='$id', " + - "apObject=$apObject, " + - "published=$published" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt deleted file mode 100644 index f65749ef..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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. - */ - -@file:Suppress("ClassName") - -package dev.usbharu.hideout.activitypub.domain.model.nodeinfo - -@Suppress("ClassNaming") -data class Nodeinfo2_0( - val version: String, - val software: Software, - val protocols: List, - val services: Services, - val openRegistrations: Boolean, - val usage: Usage, - val metadata: Metadata -) { - data class Software( - val name: String, - val version: String - ) - - data class Services( - val inbound: List, - val outbound: List - ) - - data class Usage( - val users: Users, - val localPosts: Int, - val localComments: Int - ) { - data class Users( - val total: Int, - val activeHalfYear: Int, - val activeMonth: Int - ) - } - - data class Metadata( - val nodeName: String, - val nodeDescription: String, - val maintainer: Maintainer, - val langs: List, - val tosUrl: String, - val repositoryUrl: String, - val feedbackUrl: String, - ) { - data class Maintainer( - val name: String, - val email: String - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt deleted file mode 100644 index c40b37fb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.activitypub.domain.model.objects - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import dev.usbharu.hideout.activitypub.domain.model.JsonLd - -open class Object : JsonLd { - @JsonSerialize(using = TypeSerializer::class) - var type: List = emptyList() - set(value) { - field = value.filter { it.isNotBlank() } - } - - protected constructor() - constructor(type: List) : super() { - this.type = type.filter { it.isNotBlank() } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Object - - return type == other.type - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + type.hashCode() - return result - } - - override fun toString(): String = "Object(type=$type) ${super.toString()}" - - companion object { - @JvmStatic - protected fun add(list: List, type: String): List { - val toMutableList = list.toMutableList() - toMutableList.add(type) - return toMutableList.distinct() - } - } -} - -class TypeSerializer : JsonSerializer>() { - override fun serialize(value: List?, gen: JsonGenerator?, serializers: SerializerProvider?) { - if (value?.size == 1) { - gen?.writeString(value[0]) - } else { - gen?.writeStartArray() - value?.forEach { - gen?.writeString(it) - } - gen?.writeEndArray() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt deleted file mode 100644 index 1a554745..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.activitypub.domain.model.objects - -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import dev.usbharu.hideout.activitypub.domain.model.* -import dev.usbharu.hideout.activitypub.service.common.ExtendedActivityVocabulary - -class ObjectDeserializer : JsonDeserializer() { - @Suppress("LongMethod", "CyclomaticComplexMethod") - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object? { - requireNotNull(p) - val treeNode: JsonNode = requireNotNull(p.codec?.readTree(p)) - if (treeNode.isValueNode) { - return ObjectValue( - emptyList(), - treeNode.asText() - ) - } else if (treeNode.isObject) { - val type = treeNode["type"] - val activityType = if (type.isArray) { - type.firstNotNullOf { jsonNode: JsonNode -> - ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } - } - } else if (type.isValueNode) { - ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(type.asText(), true) } - } else { - null - } - - return when (activityType) { - ExtendedActivityVocabulary.Follow -> p.codec.treeToValue(treeNode, Follow::class.java) - ExtendedActivityVocabulary.Note -> p.codec.treeToValue(treeNode, Note::class.java) - ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java) - ExtendedActivityVocabulary.Link -> null - ExtendedActivityVocabulary.Activity -> null - ExtendedActivityVocabulary.IntransitiveActivity -> null - ExtendedActivityVocabulary.Collection -> null - ExtendedActivityVocabulary.OrderedCollection -> null - ExtendedActivityVocabulary.CollectionPage -> null - ExtendedActivityVocabulary.OrderedCollectionPage -> null - ExtendedActivityVocabulary.Accept -> p.codec.treeToValue(treeNode, Accept::class.java) - ExtendedActivityVocabulary.Add -> null - ExtendedActivityVocabulary.Announce -> p.codec.treeToValue(treeNode, Announce::class.java) - ExtendedActivityVocabulary.Arrive -> null - ExtendedActivityVocabulary.Block -> null - ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java) - ExtendedActivityVocabulary.Delete -> p.codec.treeToValue(treeNode, Delete::class.java) - ExtendedActivityVocabulary.Dislike -> null - ExtendedActivityVocabulary.Flag -> null - ExtendedActivityVocabulary.Ignore -> null - ExtendedActivityVocabulary.Invite -> null - ExtendedActivityVocabulary.Join -> null - ExtendedActivityVocabulary.Leave -> null - ExtendedActivityVocabulary.Like -> p.codec.treeToValue(treeNode, Like::class.java) - ExtendedActivityVocabulary.Listen -> null - ExtendedActivityVocabulary.Move -> null - ExtendedActivityVocabulary.Offer -> null - ExtendedActivityVocabulary.Question -> null - ExtendedActivityVocabulary.Reject -> p.codec.treeToValue(treeNode, Reject::class.java) - ExtendedActivityVocabulary.Read -> null - ExtendedActivityVocabulary.Remove -> null - ExtendedActivityVocabulary.TentativeReject -> null - ExtendedActivityVocabulary.TentativeAccept -> null - ExtendedActivityVocabulary.Travel -> null - ExtendedActivityVocabulary.Undo -> p.codec.treeToValue(treeNode, Undo::class.java) - ExtendedActivityVocabulary.Update -> null - ExtendedActivityVocabulary.View -> null - ExtendedActivityVocabulary.Application -> null - ExtendedActivityVocabulary.Group -> null - ExtendedActivityVocabulary.Organization -> null - ExtendedActivityVocabulary.Person -> p.codec.treeToValue(treeNode, Person::class.java) - ExtendedActivityVocabulary.Service -> null - ExtendedActivityVocabulary.Article -> null - ExtendedActivityVocabulary.Audio -> null - ExtendedActivityVocabulary.Document -> p.codec.treeToValue(treeNode, Document::class.java) - ExtendedActivityVocabulary.Event -> null - ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java) - ExtendedActivityVocabulary.Page -> null - ExtendedActivityVocabulary.Place -> null - ExtendedActivityVocabulary.Profile -> null - ExtendedActivityVocabulary.Relationship -> null - ExtendedActivityVocabulary.Tombstone -> p.codec.treeToValue(treeNode, Tombstone::class.java) - ExtendedActivityVocabulary.Video -> null - ExtendedActivityVocabulary.Mention -> null - ExtendedActivityVocabulary.Emoji -> p.codec.treeToValue(treeNode, Emoji::class.java) - null -> null - } - } else { - return null - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt deleted file mode 100644 index 6bb2c9d5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.activitypub.domain.model.objects - -import com.fasterxml.jackson.annotation.JsonCreator - -@Suppress("VariableNaming") -open class ObjectValue @JsonCreator constructor(type: List, var `object`: String) : Object( - type -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as ObjectValue - - return `object` == other.`object` - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + `object`.hashCode() - return result - } - - override fun toString(): String = "ObjectValue(`object`='$`object`') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt deleted file mode 100644 index 3760a991..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.activitypub.domain.model.webfinger - -data class WebFinger(val subject: String, val links: List) { - data class Link(val rel: String, val type: String, val href: String) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt deleted file mode 100644 index 7807131d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.activitypub.infrastructure.exposedquery - -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.query.AnnounceQueryService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.selectAll -import org.springframework.stereotype.Repository -import java.time.Instant - -@Repository -class ExposedAnnounceQueryService( - private val postRepository: PostRepository, - private val postResultRowMapper: ResultRowMapper -) : AnnounceQueryService { - override suspend fun findById(id: Long): Pair? { - return Posts - .leftJoin(Actors) - .selectAll().where { Posts.id eq id } - .singleOrNull() - ?.let { (it.toAnnounce() ?: return null) to (postResultRowMapper.map(it)) } - } - - override suspend fun findByApId(apId: String): Pair? { - return Posts - .leftJoin(Actors) - .selectAll().where { Posts.apId eq apId } - .singleOrNull() - ?.let { (it.toAnnounce() ?: return null) to (postResultRowMapper.map(it)) } - } - - private suspend fun ResultRow.toAnnounce(): Announce? { - val repostId = this[Posts.repostId] ?: return null - val repost = postRepository.findById(repostId)?.url ?: return null - - val (to, cc) = visibility( - Visibility.entries.first { visibility -> visibility.ordinal == this[Posts.visibility] }, - this[Actors.followers] - ) - - return Announce( - type = emptyList(), - id = this[Posts.apId], - apObject = repost, - actor = this[Actors.url], - published = Instant.ofEpochMilli(this[Posts.createdAt]).toString(), - to = to, - cc = cc - ) - } - - private fun visibility(visibility: Visibility, followers: String?): Pair, List> { - return when (visibility) { - Visibility.PUBLIC -> listOf(APNoteServiceImpl.public) to listOf(APNoteServiceImpl.public) - Visibility.UNLISTED -> listOfNotNull(followers) to listOf(APNoteServiceImpl.public) - Visibility.FOLLOWERS -> listOfNotNull(followers) to listOfNotNull(followers) - Visibility.DIRECT -> TODO() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt deleted file mode 100644 index 85ee76ae..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.activitypub.infrastructure.exposedquery - -import dev.usbharu.hideout.activitypub.domain.model.Document -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.infrastructure.exposedrepository.* -import org.jetbrains.exposed.sql.Query -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.selectAll -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository -import java.time.Instant - -@Repository -class NoteQueryServiceImpl(private val postRepository: PostRepository, private val postQueryMapper: QueryMapper) : - NoteQueryService { - override suspend fun findById(id: Long): Pair? { - return Posts - .leftJoin(Actors) - .leftJoin(PostsMedia) - .leftJoin(Media) - .selectAll().where { Posts.id eq id } - .let { - (it.toNote() ?: return null) to ( - postQueryMapper.map(it) - .singleOrNull() ?: return null - ) - } - } - - override suspend fun findByApid(apId: String): Pair? { - return Posts - .leftJoin(Actors) - .leftJoin(PostsMedia) - .leftJoin(Media) - .selectAll().where { Posts.apId eq apId } - .let { - (it.toNote() ?: return null) to ( - postQueryMapper.map(it) - .singleOrNull() ?: return null - ) - } - } - - private suspend fun ResultRow.toNote(mediaList: List): Note { - val replyId = this[Posts.replyId] - val replyTo = if (replyId != null) { - val url = postRepository.findById(replyId)?.url - if (url == null) { - logger.warn("Failed to get replyId: $replyId") - } - url - } else { - null - } - - val repostId = this[Posts.repostId] - val repost = if (repostId != null) { - val url = postRepository.findById(repostId)?.url - if (url == null) { - logger.warn("Failed to get repostId: $repostId") - } - url - } else { - null - } - - val visibility1 = - visibility( - Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] }, - this[Actors.followers] - ) - return Note( - id = this[Posts.apId], - attributedTo = this[Actors.url], - content = this[Posts.text], - published = Instant.ofEpochMilli(this[Posts.createdAt]).toString(), - to = visibility1.first, - cc = visibility1.second, - inReplyTo = replyTo, - misskeyQuote = repost, - quoteUri = repost, - quoteUrl = repost, - sensitive = this[Posts.sensitive], - attachment = mediaList.map { Document(url = it.url, mediaType = "image/jpeg") } - ) - } - - private suspend fun Query.toNote(): Note? { - return this.groupBy { it[Posts.id] } - .map { it.value } - .map { it.first().toNote(it.mapNotNull { resultRow -> resultRow.toMediaOrNull() }) } - .singleOrNull() - } - - private fun visibility(visibility: Visibility, followers: String?): Pair, List> { - return when (visibility) { - Visibility.PUBLIC -> listOf(public) to listOf(public) - Visibility.UNLISTED -> listOfNotNull(followers) to listOf(public) - Visibility.FOLLOWERS -> listOfNotNull(followers) to listOfNotNull(followers) - Visibility.DIRECT -> TODO() - } - } - - companion object { - private val logger = LoggerFactory.getLogger(NoteQueryServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt deleted file mode 100644 index a51a7683..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.activitypub.interfaces.api.actor - -import dev.usbharu.hideout.activitypub.domain.model.Person -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -interface UserAPController { - @GetMapping("/users/{username}") - suspend fun userAp(@PathVariable("username") username: String): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt deleted file mode 100644 index 73b9b8a5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.activitypub.interfaces.api.actor - -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController { - override suspend fun userAp(username: String): ResponseEntity { - val person = try { - apUserService.getPersonByName(username) - } catch (_: UserNotFoundException) { - return ResponseEntity.notFound().build() - } - person.context += Constant.context - return ResponseEntity(person, HttpStatus.OK) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt deleted file mode 100644 index f89b9260..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.activitypub.interfaces.api.hostmeta - -import dev.usbharu.hideout.application.config.ApplicationConfig -import org.intellij.lang.annotations.Language -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -class HostMetaController(private val applicationConfig: ApplicationConfig) { - - val xml = //language=XML - """ - - -""" - - @Language("JSON") - val json = """{ - "links": [ - { - "rel": "lrdd", - "type": "application/jrd+json", - "template": "${applicationConfig.url}/.well-known/webfinger?resource={uri}" - } - ] -}""" - - @GetMapping("/.well-known/host-meta", produces = ["application/xml"]) - fun hostmeta(): ResponseEntity = ResponseEntity(xml, HttpStatus.OK) - - @GetMapping("/.well-known/host-meta.json", produces = ["application/json"]) - fun hostmetJson(): ResponseEntity = ResponseEntity(json, HttpStatus.OK) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt deleted file mode 100644 index 2cbbdbec..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.activitypub.interfaces.api.inbox - -import jakarta.servlet.http.HttpServletRequest -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RestController - -@RestController -interface InboxController { - @RequestMapping( - "/inbox", - "/users/{username}/inbox", - produces = [ - "application/activity+json", - "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" - ], - consumes = ["application/json", "application/*+json"], - method = [RequestMethod.POST] - ) - suspend fun inbox(httpServletRequest: HttpServletRequest): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt deleted file mode 100644 index 5045007b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.activitypub.interfaces.api.inbox - -import dev.usbharu.hideout.activitypub.service.common.APService -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import jakarta.servlet.http.HttpServletRequest -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.slf4j.MDCContext -import kotlinx.coroutines.withContext -import org.slf4j.LoggerFactory -import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController -import java.net.URL - -@RestController -class InboxControllerImpl( - private val apService: APService, - private val httpSignatureHeaderChecker: HttpSignatureHeaderChecker, -) : InboxController { - @Suppress("TooGenericExceptionCaught") - override suspend fun inbox( - httpServletRequest: HttpServletRequest, - ): ResponseEntity { - val headersList = httpServletRequest.headerNames?.toList().orEmpty() - LOGGER.trace("Inbox Headers {}", headersList) - - val body = withContext(Dispatchers.IO + MDCContext()) { - httpServletRequest.inputStream.readAllBytes()!! - } - - val responseEntity = checkHeader(httpServletRequest, body) - - if (responseEntity != null) { - return responseEntity - } - - val parseActivity = try { - apService.parseActivity(body.decodeToString()) - } catch (e: Exception) { - LOGGER.warn("FAILED Parse Activity", e) - return ResponseEntity.accepted().build() - } - LOGGER.info("INBOX Processing Activity Type: {}", parseActivity) - try { - val url = httpServletRequest.requestURL.toString() - - val headers = - headersList.associateWith { header -> - httpServletRequest.getHeaders(header)?.toList().orEmpty() - } - - apService.processActivity( - body.decodeToString(), - parseActivity, - HttpRequest( - URL(url + httpServletRequest.queryString.orEmpty()), - HttpHeaders(headers), - HttpMethod.POST - ), - headers - ) - } catch (e: Exception) { - LOGGER.warn("FAILED Process Activity $parseActivity", e) - return ResponseEntity(HttpStatus.ACCEPTED) - } - LOGGER.info("SUCCESS Processing Activity Type: {}", parseActivity) - return ResponseEntity(HttpStatus.ACCEPTED) - } - - private fun checkHeader( - httpServletRequest: HttpServletRequest, - body: ByteArray, - ): ResponseEntity? { - try { - httpSignatureHeaderChecker.checkDate(httpServletRequest.getHeader("date")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required date header") - } catch (_: IllegalArgumentException) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Request is too old.") - } - try { - httpSignatureHeaderChecker.checkHost(httpServletRequest.getHeader("host")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required host header") - } catch (_: IllegalArgumentException) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Wrong host for request") - } - try { - httpSignatureHeaderChecker.checkDigest(body, httpServletRequest.getHeader("digest")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body("Required request body digest in digest header (sha256)") - } catch (_: IllegalArgumentException) { - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) - .body("Wrong digest for request") - } - - if (httpServletRequest.getHeader("signature").orEmpty().isBlank()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .header( - WWW_AUTHENTICATE, - "Signature realm=\"Example\",headers=\"(request-target) date host digest\"" - ) - .build() - } - return null - } - - companion object { - private val LOGGER = LoggerFactory.getLogger(InboxControllerImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt deleted file mode 100644 index 00cda72e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.activitypub.interfaces.api.nodeinfo - -import dev.usbharu.hideout.activitypub.domain.model.nodeinfo.Nodeinfo -import dev.usbharu.hideout.activitypub.domain.model.nodeinfo.Nodeinfo2_0 -import dev.usbharu.hideout.application.config.ApplicationConfig -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -class NodeinfoController(private val applicationConfig: ApplicationConfig) { - @GetMapping("/.well-known/nodeinfo") - fun nodeinfo(): ResponseEntity { - return ResponseEntity( - Nodeinfo( - listOf( - Nodeinfo.Links( - "http://nodeinfo.diaspora.software/ns/schema/2.0", - "${applicationConfig.url}/nodeinfo/2.0" - ) - ) - ), - HttpStatus.OK - ) - } - - @GetMapping("/nodeinfo/2.0") - @Suppress("FunctionNaming") - fun nodeinfo2_0(): ResponseEntity { - return ResponseEntity( - Nodeinfo2_0( - version = "2.0", - software = Nodeinfo2_0.Software( - name = "hideout", - version = "0.0.1" - ), - protocols = listOf("activitypub"), - services = Nodeinfo2_0.Services( - inbound = emptyList(), - outbound = emptyList() - ), - openRegistrations = false, - usage = Nodeinfo2_0.Usage( - users = Nodeinfo2_0.Usage.Users( - total = 1, - activeHalfYear = 1, - activeMonth = 1 - ), - localPosts = 1, - localComments = 0 - ), - metadata = Nodeinfo2_0.Metadata( - nodeName = "hideout", - nodeDescription = "hideout test server", - maintainer = Nodeinfo2_0.Metadata.Maintainer("usbharu", "i@usbharu.dev"), - langs = emptyList(), - tosUrl = "", - repositoryUrl = "https://github.com/usbharu/Hideout", - feedbackUrl = "https://github.com/usbharu/Hideout/issues/new/choose", - ) - ), - HttpStatus.OK - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt deleted file mode 100644 index 2150171a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.activitypub.interfaces.api.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable - -interface NoteApController { - @GetMapping("/users/*/posts/{postId}") - suspend fun postsAp( - @PathVariable("postId") postId: Long - ): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt deleted file mode 100644 index 79e54dcb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.activitypub.interfaces.api.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.service.objects.note.NoteApApiService -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser -import org.springframework.http.ResponseEntity -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -class NoteApControllerImpl(private val noteApApiService: NoteApApiService) : NoteApController { - override suspend fun postsAp( - @PathVariable(value = "postId") postId: Long, - ): ResponseEntity { - val context = SecurityContextHolder.getContext() - val userId = - if (context.authentication is PreAuthenticatedAuthenticationToken && - context.authentication.details is HttpSignatureUser - ) { - (context.authentication.details as HttpSignatureUser).id - } else { - null - } - - val note = noteApApiService.getNote(postId, userId) - if (note != null) { - return ResponseEntity.ok(note) - } - return ResponseEntity.notFound().build() - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt deleted file mode 100644 index 0574dd34..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.activitypub.interfaces.api.outbox - -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RestController - -@RestController -interface OutboxController { - @RequestMapping("/outbox", "/users/{username}/outbox", method = [RequestMethod.POST, RequestMethod.GET]) - suspend fun outbox(): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt deleted file mode 100644 index b9ebbbc4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.activitypub.interfaces.api.outbox - -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class OutboxControllerImpl : OutboxController { - override suspend fun outbox(): ResponseEntity = - ResponseEntity(HttpStatus.NOT_IMPLEMENTED) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt deleted file mode 100644 index a6d65710..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.activitypub.interfaces.api.webfinger - -import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger -import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.util.AcctUtil -import kotlinx.coroutines.runBlocking -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestParam - -@Controller -class WebFingerController( - private val webFingerApiService: WebFingerApiService, - private val applicationConfig: ApplicationConfig -) { - @GetMapping("/.well-known/webfinger") - fun webfinger(@RequestParam("resource") resource: String): ResponseEntity = runBlocking { - logger.info("WEBFINGER Lookup webfinger resource: {}", resource) - val acct = try { - AcctUtil.parse(resource.replace("acct:", "")) - } catch (e: IllegalArgumentException) { - logger.warn("FAILED Parse acct.", e) - return@runBlocking ResponseEntity.badRequest().build() - } - val user = try { - webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host) - } catch (_: UserNotFoundException) { - return@runBlocking ResponseEntity.notFound().build() - } - val webFinger = WebFinger( - "acct:${user.name}@${user.domain}", - listOf( - WebFinger.Link( - "self", - "application/activity+json", - user.url - ) - ) - ) - logger.info("SUCCESS Lookup webfinger resource: {} acct: {}", resource, acct) - ResponseEntity(webFinger, HttpStatus.OK) - } - - companion object { - private val logger = LoggerFactory.getLogger(WebFingerController::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt deleted file mode 100644 index 7df08bb3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.activitypub.service.activity.accept - -import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import org.springframework.stereotype.Service - -@Service -class ApAcceptProcessor( - transaction: Transaction, - private val relationshipService: RelationshipService, - private val actorRepository: ActorRepository -) : - AbstractActivityPubProcessor(transaction) { - - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val value = activity.activity.apObject - - if (value.type.contains("Follow").not()) { - logger.warn("FAILED Activity type isn't Follow.") - throw IllegalActivityPubObjectException("Invalid type ${value.type}") - } - - val follow = value as Follow - - val userUrl = follow.apObject - val followerUrl = follow.actor - - val user = actorRepository.findByUrl(userUrl) ?: throw UserNotFoundException.withUrl(userUrl) - val follower = actorRepository.findByUrl(followerUrl) ?: throw UserNotFoundException.withUrl(followerUrl) - - relationshipService.acceptFollowRequest(user.id, follower.id) - logger.debug("SUCCESS Follow from ${user.url} to ${follower.url}.") - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Accept - - override fun type(): Class = Accept::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt deleted file mode 100644 index ea1793e6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.activitypub.service.activity.accept - -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverAcceptTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -interface ApSendAcceptService { - suspend fun sendAcceptFollow(actor: Actor, target: Actor) -} - -@Service -class ApSendAcceptServiceImpl( - private val owlProducer: OwlProducer, -) : ApSendAcceptService { - override suspend fun sendAcceptFollow(actor: Actor, target: Actor) { - val deliverAcceptTask = DeliverAcceptTask( - Accept( - apObject = Follow( - apObject = actor.url, - actor = target.url - ), - actor = actor.url - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverAcceptTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt deleted file mode 100644 index 17ff0cac..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.activitypub.service.activity.announce - -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.application.external.Transaction -import org.springframework.stereotype.Service - -@Service -class ApAnnounceProcessor(transaction: Transaction, private val apNoteService: APNoteService) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - apNoteService.fetchAnnounce(activity.activity) - } - - override fun isSupported(activityType: ActivityType): Boolean = ActivityType.Announce == activityType - - override fun type(): Class = Announce::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt deleted file mode 100644 index 0fb4a1e8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.activitypub.service.activity.create - -import dev.usbharu.hideout.activitypub.domain.model.Create -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.external.job.DeliverCreateTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class ApSendCreateServiceImpl( - private val followerQueryService: FollowerQueryService, - private val noteQueryService: NoteQueryService, - private val applicationConfig: ApplicationConfig, - private val actorRepository: ActorRepository, - private val owlProducer: OwlProducer, -) : ApSendCreateService { - override suspend fun createNote(post: Post) { - logger.info("CREATE Create Local Note ${post.url}") - logger.debug("START Create Local Note ${post.url}") - logger.trace("{}", post) - val followers = followerQueryService.findFollowersById(post.actorId) - - logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.") - - val userEntity = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - val note = noteQueryService.findById(post.id)?.first ?: throw PostNotFoundException.withId(post.id) - val create = Create( - name = "Create Note", - apObject = note, - actor = note.attributedTo, - id = "${applicationConfig.url}/create/note/${post.id}" - ) - followers.forEach { followerEntity -> - owlProducer.publishTask(DeliverCreateTask(create, userEntity.url, followerEntity.inbox)) - } - - logger.debug("SUCCESS Create Local Note ${post.url}") - } - - companion object { - private val logger = LoggerFactory.getLogger(ApSendCreateServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt deleted file mode 100644 index 9be6fa65..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.activitypub.service.activity.create - -import dev.usbharu.hideout.activitypub.domain.model.Create -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.application.external.Transaction -import org.springframework.stereotype.Service - -@Service -class CreateActivityProcessor(transaction: Transaction, private val apNoteService: APNoteService) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - apNoteService.fetchNote(activity.activity.apObject as Note) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Create - - override fun type(): Class = Create::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt deleted file mode 100644 index a0d789c5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.activitypub.service.activity.delete - -import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException -import dev.usbharu.hideout.activitypub.domain.model.Delete -import dev.usbharu.hideout.activitypub.domain.model.HasId -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -@Service -class APDeleteProcessor( - transaction: Transaction, - private val userService: UserService, - private val postService: PostService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val value = activity.activity.apObject - val deleteId = if (value is HasId) { - value.id - } else if (value is ObjectValue) { - value.`object` - } else { - throw IllegalActivityPubObjectException("object hasn't id or object") - } - - val actor = actorRepository.findByUrl(deleteId) - actor?.let { userService.deleteRemoteActor(it.id) } - - val post = postRepository.findByApId(deleteId) - if (post == null) { - logger.warn("FAILED Delete id: {} is not found.", deleteId) - return - } - postService.deleteRemote(post) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete - - override fun type(): Class = Delete::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt deleted file mode 100644 index 4570808d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.activitypub.service.activity.delete - -import dev.usbharu.hideout.activitypub.domain.model.Delete -import dev.usbharu.hideout.activitypub.domain.model.Tombstone -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -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.post.Post -import dev.usbharu.hideout.core.external.job.DeliverDeleteTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -interface APSendDeleteService { - suspend fun sendDeleteNote(deletedPost: Post) - suspend fun sendDeleteActor(deletedActor: Actor) -} - -@Service -class APSendDeleteServiceImpl( - private val followerQueryService: FollowerQueryService, - private val applicationConfig: ApplicationConfig, - private val actorRepository: ActorRepository, - private val owlProducer: OwlProducer, -) : APSendDeleteService { - override suspend fun sendDeleteNote(deletedPost: Post) { - val actor = - actorRepository.findById(deletedPost.actorId) ?: throw UserNotFoundException.withId(deletedPost.actorId) - val followersById = followerQueryService.findFollowersById(deletedPost.actorId) - - val delete = Delete( - actor = actor.url, - id = "${applicationConfig.url}/delete/note/${deletedPost.id}", - published = Instant.now().toString(), - `object` = Tombstone(id = deletedPost.apId) - ) - - followersById.forEach { - val jobProps = DeliverDeleteTask( - delete, - it.inbox, - actor.id - ) - - owlProducer.publishTask(jobProps) - } - } - - override suspend fun sendDeleteActor(deletedActor: Actor) { - val followers = followerQueryService.findFollowersById(deletedActor.id) - - val delete = Delete( - actor = deletedActor.url, - `object` = ObjectValue(emptyList(), `object` = deletedActor.url), - id = "${applicationConfig.url}/delete/actor/${deletedActor.id}", - published = Instant.now().toString() - ) - - followers.forEach { - DeliverDeleteTask( - delete = delete, - it.inbox, - deletedActor.id - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt deleted file mode 100644 index 3cc8cc68..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.external.job.ReceiveFollowTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -@Service -class APFollowProcessor( - transaction: Transaction, - private val owlProducer: OwlProducer, -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - logger.info("FOLLOW from: {} to {}", activity.activity.actor, activity.activity.apObject) - - // inboxをジョブキューに乗せているので既に不要だが、フォロー承認制アカウントを実装する際に必要なので残す - val jobProps = ReceiveFollowTask( - activity.activity.actor, - activity.activity, - activity.activity.apObject - ) - owlProducer.publishTask(jobProps) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Follow - - override fun type(): Class = Follow::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt deleted file mode 100644 index 9d69e7be..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.core.external.job.ReceiveFollowTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -interface APReceiveFollowService { - suspend fun receiveFollow(follow: Follow) -} - -@Service -class APReceiveFollowServiceImpl( - private val owlProducer: OwlProducer, -) : APReceiveFollowService { - override suspend fun receiveFollow(follow: Follow) { - logger.info("FOLLOW from: {} to: {}", follow.actor, follow.apObject) - owlProducer.publishTask(ReceiveFollowTask(follow.actor, follow, follow.apObject)) - return - } - - companion object { - private val logger = LoggerFactory.getLogger(APReceiveFollowServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt deleted file mode 100644 index 80ac8c7f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.service.follow.SendFollowDto -import org.springframework.stereotype.Service - -interface APSendFollowService { - suspend fun sendFollow(sendFollowDto: SendFollowDto) -} - -@Service -class APSendFollowServiceImpl( - private val apRequestService: APRequestService, - private val applicationConfig: ApplicationConfig, -) : APSendFollowService { - override suspend fun sendFollow(sendFollowDto: SendFollowDto) { - val follow = Follow( - apObject = sendFollowDto.followTargetActorId.url, - actor = sendFollowDto.actorId.url, - id = "${applicationConfig.url}/follow/${sendFollowDto.actorId.id}/${sendFollowDto.followTargetActorId.id}" - ) - - apRequestService.apPost(sendFollowDto.followTargetActorId.inbox, follow, sendFollowDto.actorId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt deleted file mode 100644 index b018b6d4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.activitypub.service.activity.like - -import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.service.reaction.ReactionService -import org.springframework.stereotype.Service - -@Service -class APLikeProcessor( - transaction: Transaction, - private val apUserService: APUserService, - private val apNoteService: APNoteService, - private val reactionService: ReactionService, - private val emojiService: EmojiService -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val actor = activity.activity.actor - val content = activity.activity.content - - val target = activity.activity.apObject - - val personWithEntity = apUserService.fetchPersonWithEntity(actor) - - try { - val post = apNoteService.fetchNoteWithEntity(target).second - - val emoji = if (content.startsWith(":")) { - val tag = activity.activity.tag - (tag.firstOrNull { it is Emoji } as? Emoji)?.let { emojiService.fetchEmoji(it).second } - } else { - UnicodeEmoji(content) - } - - reactionService.receiveReaction( - emoji ?: UnicodeEmoji("❤"), - personWithEntity.second.id, - post.id - ) - - logger.debug("SUCCESS Add Like($content) from ${personWithEntity.second.url} to ${post.url}") - } catch (e: FailedToGetActivityPubResourceException) { - logger.debug("FAILED failed to get {}", target) - logger.trace("", e) - return - } - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Like - - override fun type(): Class = Like::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt deleted file mode 100644 index fac9ccff..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.activitypub.service.activity.like - -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.hideout.activitypub.domain.model.Undo -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.Reaction -import dev.usbharu.hideout.core.external.job.DeliverReactionTask -import dev.usbharu.hideout.core.external.job.DeliverUndoTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -interface APReactionService { - suspend fun reaction(like: Reaction) - suspend fun removeReaction(like: Reaction) -} - -@Service -class APReactionServiceImpl( - private val followerQueryService: FollowerQueryService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : APReactionService { - override suspend fun reaction(like: Reaction) { - val followers = followerQueryService.findFollowersById(like.actorId) - val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId) - val post = - postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId) - followers.forEach { follower -> - owlProducer.publishTask( - DeliverReactionTask( - actor = user.url, - like = Like( - actor = user.url, - id = "${applicationConfig.url}/like/note/${post.id}", - content = "❤", - apObject = post.url - ), - inbox = follower.inbox - ) - ) - } - } - - override suspend fun removeReaction(like: Reaction) { - val followers = followerQueryService.findFollowersById(like.actorId) - val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId) - val post = - postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId) - followers.forEach { follower -> - owlProducer.publishTask( - DeliverUndoTask( - signer = user.id, - inbox = follower.inbox, - undo = Undo( - actor = user.url, - id = "${applicationConfig.url}/undo/like/${post.id}", - apObject = Like( - actor = user.url, - id = "${applicationConfig.url}/like/note/${post.id}", - content = "❤", - apObject = post.url - ), - published = Instant.now().toString(), - ) - ) - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt deleted file mode 100644 index 9a3d5bb0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.activitypub.service.activity.reject - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Reject -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import org.springframework.stereotype.Service - -@Service -class ApRejectProcessor( - private val relationshipService: RelationshipService, - transaction: Transaction, - private val actorRepository: ActorRepository -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val activityType = activity.activity.apObject.type.firstOrNull { it == "Follow" } - - if (activityType == null) { - logger.warn("FAILED Process Reject Activity type: {}", activity.activity.apObject.type) - return - } - when (activityType) { - "Follow" -> { - val user = actorRepository.findByUrl(activity.activity.actor) ?: throw UserNotFoundException.withUrl( - activity.activity.actor - ) - - activity.activity.apObject as Follow - - val actor = activity.activity.apObject.actor - - val target = actorRepository.findByUrl(actor) ?: throw UserNotFoundException.withUrl(actor) - - logger.debug("REJECT Follow user {} target {}", user.url, target.url) - - relationshipService.rejectFollowRequest(user.id, target.id) - } - - else -> {} - } - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Reject - - override fun type(): Class = Reject::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt deleted file mode 100644 index eef8dc2f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.activitypub.service.activity.reject - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Reject -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverRejectTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -@Service -class ApSendRejectServiceImpl( - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : ApSendRejectService { - override suspend fun sendRejectFollow(actor: Actor, target: Actor) { - val deliverRejectTask = DeliverRejectTask( - Reject( - actor.url, - "${applicationConfig.url}/reject/${actor.id}/${target.id}", - Follow(apObject = actor.url, actor = target.url) - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverRejectTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt deleted file mode 100644 index 37326c0d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.activitypub.service.activity.undo - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Undo -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverUndoTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class APSendUndoServiceImpl( - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : APSendUndoService { - override suspend fun sendUndoFollow(actor: Actor, target: Actor) { - val deliverUndoTask = DeliverUndoTask( - Undo( - actor = actor.url, - id = "${applicationConfig.url}/undo/follow/${actor.id}/${target.url}", - apObject = Follow( - apObject = actor.url, - actor = target.url - ), - published = Instant.now().toString() - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverUndoTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt deleted file mode 100644 index e788929e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.activitypub.service.activity.undo - -import dev.usbharu.hideout.activitypub.domain.model.* -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.local.LocalUserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.reaction.ReactionService -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -@Service -class APUndoProcessor( - transaction: Transaction, - private val apUserService: APUserService, - private val relationshipService: RelationshipService, - private val reactionService: ReactionService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val postService: PostService, - private val userService: UserService, -) : AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val undo = activity.activity - - val type = undo.apObject.type.firstOrNull { - it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" - } ?: return - - when (type) { - "Follow" -> { - follow(undo) - return - } - - "Accept" -> { - accept(undo) - return - } - - "Like" -> { - like(undo) - return - } - - "Announce" -> { - announce(undo) - return - } - - "Delete" -> { - delete(undo) - return - } - - else -> {} - } - TODO() - } - - private suspend fun accept(undo: Undo) { - val accept = undo.apObject as Accept - - val acceptObject = if (accept.apObject is ObjectValue) { - accept.apObject.`object` - } else if (accept.apObject is Follow) { - accept.apObject.apObject - } else { - logger.warn("FAILED Unsupported type. Undo Accept {}", accept.apObject.type) - return - } - - val accepter = apUserService.fetchPersonWithEntity(undo.actor, acceptObject).second - val target = actorRepository.findByUrl(acceptObject) ?: throw UserNotFoundException.withUrl(acceptObject) - - relationshipService.rejectFollowRequest(accepter.id, target.id) - return - } - - private suspend fun like(undo: Undo) { - val like = undo.apObject as Like - - val post = postRepository.findByUrl(like.apObject) ?: throw PostNotFoundException.withUrl(like.apObject) - - val signer = actorRepository.findById(post.actorId) ?: throw LocalUserNotFoundException.withId(post.actorId) - val actor = apUserService.fetchPersonWithEntity(like.actor, signer.url).second - - reactionService.receiveRemoveReaction(actor.id, post.id) - return - } - - private suspend fun follow(undo: Undo) { - val follow = undo.apObject as Follow - - val follower = apUserService.fetchPersonWithEntity(undo.actor, follow.apObject).second - val target = actorRepository.findByUrl(follow.apObject) ?: throw UserNotFoundException.withUrl(follow.apObject) - - relationshipService.unfollow(follower.id, target.id) - return - } - - private suspend fun announce(undo: Undo) { - val announce = undo.apObject as Announce - - val findByApId = postRepository.findByApId(announce.id) ?: return - postService.deleteRemote(findByApId) - } - - private suspend fun delete(undo: Undo) { - val announce = undo.apObject as Delete - - val actor = actorRepository.findByUrl(announce.actor) ?: throw UserNotFoundException.withUrl(announce.actor) - - userService.restorationRemoteActor(actor.id) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo - - override fun type(): Class = Undo::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt deleted file mode 100644 index 2507e3cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APRequestService { - suspend fun apGet(url: String, signer: Actor? = null, responseClass: Class): R - suspend fun apPost( - url: String, - body: T? = null, - signer: Actor? = null, - responseClass: Class - ): R - - suspend fun apPost(url: String, body: T? = null, signer: Actor? = null): String -} - -suspend inline fun APRequestService.apGet(url: String, signer: Actor? = null): R = - apGet(url, signer, R::class.java) - -suspend inline fun APRequestService.apPost( - url: String, - body: T? = null, - signer: Actor? = null -): R = apPost(url, body, signer, R::class.java) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt deleted file mode 100644 index 4b463532..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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.activitypub.service.common - -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.util.Base64Util -import dev.usbharu.hideout.util.HttpUtil.Activity -import dev.usbharu.hideout.util.RsaUtil -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.common.PrivateKey -import dev.usbharu.httpsignature.sign.HttpSignatureSigner -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.util.* -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.net.URL -import java.security.MessageDigest -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -@Service -class APRequestServiceImpl( - private val httpClient: HttpClient, - @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val httpSignatureSigner: HttpSignatureSigner, - @Qualifier("http") private val dateTimeFormatter: DateTimeFormatter, -) : APRequestService { - - override suspend fun apGet(url: String, signer: Actor?, responseClass: Class): R { - logger.debug("START ActivityPub Request GET url: {}, signer: {}", url, signer?.url) - val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) - val u = URL(url) - val httpResponse = if (signer?.privateKey == null) { - apGetNotSign(url, date) - } else { - apGetSign(date, u, signer, url) - } - - val bodyAsText = httpResponse.bodyAsText() - val readValue = objectMapper.readValue(bodyAsText, responseClass) - logger.debug( - "SUCCESS ActivityPub Request GET status: {} url: {}", - httpResponse.status, - httpResponse.request.url - ) - logBody(bodyAsText, url) - return readValue - } - - private suspend fun apGetSign( - date: String, - u: URL, - signer: Actor, - url: String, - ): HttpResponse { - val headers = headers { - append("Accept", Activity) - append("Date", date) - append("Host", u.host) - } - - val sign = httpSignatureSigner.sign( - httpRequest = HttpRequest( - url = u, - headers = HttpHeaders(headers.toMap()), - HttpMethod.GET - ), - privateKey = PrivateKey( - keyId = "${signer.url}#pubkey", - privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!), - ), - signHeaders = listOf("(request-target)", "date", "host", "accept") - ) - - val httpResponse = httpClient.get(url) { - headers { - headers { - appendAll(headers) - append("Signature", sign.signatureHeader) -// remove("Host") - } - } - contentType(Activity) - } - return httpResponse - } - - private suspend fun apGetNotSign(url: String, date: String?) = httpClient.get(url) { - header("Accept", Activity) - header("Date", date) - } - - override suspend fun apPost( - url: String, - body: T?, - signer: Actor?, - responseClass: Class, - ): R { - val bodyAsText = apPost(url, body, signer) - return objectMapper.readValue(bodyAsText, responseClass) - } - - override suspend fun apPost(url: String, body: T?, signer: Actor?): String { - logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url) - val requestBody = addContextIfNotNull(body) - - logger.trace( - """ - | - |***** BEGIN HTTP Request Trace url: {} ***** - | - |$requestBody - | - |***** END HTTP Request Trace url: {} ***** - | - """.trimMargin(), - url, - url - ) - - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(requestBody.orEmpty().toByteArray())) - - val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) - val u = URL(url) - val httpResponse = if (signer?.privateKey == null) { - apPostNotSign(url, date, digest, requestBody) - } else { - apPostSign(date, u, digest, signer, requestBody) - } - - val bodyAsText = httpResponse.bodyAsText() - logger.debug( - "SUCCESS ActivityPub Request POST status: {} url: {}", - httpResponse.status, - httpResponse.request.url - ) - logBody(bodyAsText, url) - return bodyAsText - } - - private suspend fun apPostNotSign( - url: String, - date: String?, - digest: String, - requestBody: String?, - ) = httpClient.post(url) { - accept(Activity) - header("Date", date) - header("Digest", "sha-256=$digest") - if (requestBody != null) { - setBody(requestBody) - contentType(Activity) - } - } - - private suspend fun apPostSign( - date: String, - u: URL, - digest: String, - signer: Actor, - requestBody: String?, - ): HttpResponse { - val headers = headers { - append("Accept", Activity) - append("Date", date) - append("Host", u.host) - append("Digest", "SHA-256=$digest") - } - - val sign = httpSignatureSigner.sign( - httpRequest = HttpRequest( - u, - HttpHeaders(headers.toMap()), - HttpMethod.POST - ), - privateKey = PrivateKey( - keyId = signer.keyId, - privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!) - ), - signHeaders = listOf("(request-target)", "date", "host", "digest") - ) - - val httpResponse = httpClient.post(u) { - headers { - appendAll(headers) - append("Signature", sign.signatureHeader) -// remove("Host") - } - setBody(requestBody) - contentType(Activity) - } - return httpResponse - } - - private fun addContextIfNotNull(body: T?) = if (body != null) { - val context = mutableListOf() - context.addAll(Constant.context) - context.addAll(body.context) - body.context = context - objectMapper.writeValueAsString(body) - } else { - null - } - - private fun logBody(bodyAsText: String, url: String) { - logger.trace( - """ - | - |***** BEGIN HTTP Response Trace url: {} ***** - | - |$bodyAsText - | - |***** END HTTP Response TRACE url: {} ***** - | - """.trimMargin(), - url, - url - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(APRequestServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt deleted file mode 100644 index bd42e197..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APResourceResolveService { - suspend fun resolve(url: String, clazz: Class, singer: Actor?): T - suspend fun resolve(url: String, clazz: Class, singerId: Long?): T -} - -suspend inline fun APResourceResolveService.resolve(url: String, singer: Actor?): T = - resolve(url, T::class.java, singer) - -suspend inline fun APResourceResolveService.resolve(url: String, singerId: Long?): T = - resolve(url, T::class.java, singerId) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt deleted file mode 100644 index f40589b1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.resource.CacheManager -import dev.usbharu.hideout.core.service.resource.ResolveResponse -import org.springframework.stereotype.Service -import java.io.InputStream - -@Service -class APResourceResolveServiceImpl( - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, - private val cacheManager: CacheManager -) : - APResourceResolveService { - - override suspend fun resolve(url: String, clazz: Class, singerId: Long?): T = - internalResolve(url, singerId, clazz) - - override suspend fun resolve(url: String, clazz: Class, singer: Actor?): T = - internalResolve(url, singer, clazz) - - private suspend fun internalResolve(url: String, singerId: Long?, clazz: Class): T { - val key = genCacheKey(url, singerId) - - cacheManager.putCache(key) { - runResolve(url, singerId?.let { actorRepository.findById(it) }, clazz) - } - return (cacheManager.getOrWait(key) as APResolveResponse).objects - } - - private suspend fun internalResolve(url: String, singer: Actor?, clazz: Class): T { - val key = genCacheKey(url, singer?.id) - cacheManager.putCache(key) { - runResolve(url, singer, clazz) - } - return (cacheManager.getOrWait(key) as APResolveResponse).objects - } - - private suspend fun runResolve(url: String, singer: Actor?, clazz: Class): ResolveResponse = - APResolveResponse(apRequestService.apGet(url, singer, clazz)) - - private fun genCacheKey(url: String, singerId: Long?): String { - if (singerId != null) { - return "$url-$singerId" - } - return url - } - - private class APResolveResponse(val objects: T) : ResolveResponse { - override suspend fun body(): InputStream { - TODO("Not yet implemented") - } - - override suspend fun bodyAsText(): String { - TODO("Not yet implemented") - } - - override suspend fun bodyAsBytes(): ByteArray { - TODO("Not yet implemented") - } - - override suspend fun header(): Map> { - TODO("Not yet implemented") - } - - override suspend fun status(): Int { - TODO("Not yet implemented") - } - - override suspend fun statusMessage(): String { - TODO("Not yet implemented") - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as APResolveResponse<*> - - return objects == other.objects - } - - override fun hashCode(): Int = objects.hashCode() - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt deleted file mode 100644 index 64aa581b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.activitypub.service.common - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.activitypub.domain.exception.JsonParseException -import dev.usbharu.hideout.core.external.job.InboxTask -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service - -interface APService { - fun parseActivity(json: String): ActivityType - - suspend fun processActivity( - json: String, - type: ActivityType, - httpRequest: HttpRequest, - map: Map> - ) -} - -@Service -class APServiceImpl( - @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val owlProducer: OwlProducer, -) : APService { - - val logger: Logger = LoggerFactory.getLogger(APServiceImpl::class.java) - override fun parseActivity(json: String): ActivityType { - val readTree = try { - objectMapper.readTree(json) - } catch (e: com.fasterxml.jackson.core.JsonParseException) { - throw JsonParseException("Failed to parse json", e) - } - logger.trace( - """ - | - |***** Trace Begin Activity ***** - | - |{} - | - |***** Trace End Activity ***** - | - """.trimMargin(), - readTree.toPrettyString() - ) - if (readTree.isObject.not()) { - throw JsonParseException("Json is not object.") - } - val type = readTree["type"] ?: throw JsonParseException("Type is null") - if (type.isArray) { - try { - return type.firstNotNullOf { jsonNode: JsonNode -> - ActivityType.entries.firstOrNull { it.name.equals(jsonNode.asText(), true) } - } - } catch (e: NoSuchElementException) { - throw IllegalArgumentException("No valid TYPE", e) - } - } - try { - return ActivityType.entries.first { it.name.equals(type.asText(), true) } - } catch (e: NoSuchElementException) { - throw IllegalArgumentException("No valid TYPE", e) - } - } - - override suspend fun processActivity( - json: String, - type: ActivityType, - httpRequest: HttpRequest, - map: Map> - ) { - logger.debug("process activity: {}", type) - owlProducer.publishTask( - InboxTask( - json, - type, - httpRequest, - map - ) - ) - return - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt deleted file mode 100644 index f38d7cc1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.exception.ActivityPubProcessException -import dev.usbharu.hideout.activitypub.domain.exception.FailedProcessException -import dev.usbharu.hideout.activitypub.domain.exception.HttpSignatureUnauthorizedException -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.sql.SQLException - -abstract class AbstractActivityPubProcessor( - private val transaction: Transaction, - private val allowUnauthorized: Boolean = false -) : ActivityPubProcessor { - protected val logger: Logger = LoggerFactory.getLogger(this::class.java) - - override suspend fun process(activity: ActivityPubProcessContext) { - if (activity.isAuthorized.not() && allowUnauthorized.not()) { - throw HttpSignatureUnauthorizedException() - } - logger.info("START ActivityPub process. {}", this.type()) - try { - transaction.transaction { - try { - internalProcess(activity) - } catch (e: ResourceAccessException) { - throw SQLException(e) - } - } - } catch (e: ActivityPubProcessException) { - logger.warn("FAILED ActivityPub process", e) - throw FailedProcessException("Failed process", e) - } - logger.info("SUCCESS ActivityPub process. {}", this.type()) - } - - abstract suspend fun internalProcess(activity: ActivityPubProcessContext) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt deleted file mode 100644 index 50d07478..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.activitypub.service.common - -import com.fasterxml.jackson.databind.JsonNode -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.verify.Signature - -data class ActivityPubProcessContext( - val activity: T, - val jsonNode: JsonNode, - val httpRequest: HttpRequest, - val signature: Signature?, - val isAuthorized: Boolean -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt deleted file mode 100644 index d558e798..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -interface ActivityPubProcessor { - suspend fun process(activity: ActivityPubProcessContext) - - fun isSupported(activityType: ActivityType): Boolean - - fun type(): Class -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt deleted file mode 100644 index acd1fcb6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.activitypub.service.common - -enum class ActivityType { - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Other -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt deleted file mode 100644 index 4d42dfff..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.activitypub.service.common - -enum class ActivityVocabulary { - Object, - Link, - Activity, - IntransitiveActivity, - Collection, - OrderedCollection, - CollectionPage, - OrderedCollectionPage, - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Application, - Group, - Organization, - Person, - Service, - Article, - Audio, - Document, - Event, - Image, - Note, - Page, - Place, - Profile, - Relationship, - Tombstone, - Video, - Mention, -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt deleted file mode 100644 index b8150064..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.activitypub.service.common - -enum class ExtendedActivityVocabulary { - Object, - Link, - Activity, - IntransitiveActivity, - Collection, - OrderedCollection, - CollectionPage, - OrderedCollectionPage, - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Application, - Group, - Organization, - Person, - Service, - Article, - Audio, - Document, - Event, - Image, - Note, - Page, - Place, - Profile, - Relationship, - Tombstone, - Video, - Mention, - Emoji -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt deleted file mode 100644 index 7687dcef..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.activitypub.service.objects.emoji - -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji - -interface EmojiService { - suspend fun fetchEmoji(url: String): Pair - suspend fun fetchEmoji(emoji: Emoji): Pair - suspend fun findByEmojiName(emojiName: String): CustomEmoji? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt deleted file mode 100644 index 61ba3904..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.activitypub.service.objects.emoji - -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveServiceImpl -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository -import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.media.RemoteMedia -import org.springframework.stereotype.Service -import java.net.URL -import java.time.Instant - -@Service -class EmojiServiceImpl( - private val customEmojiRepository: CustomEmojiRepository, - private val instanceService: InstanceService, - private val mediaService: MediaService, - private val apResourceResolveServiceImpl: APResourceResolveServiceImpl, - private val applicationConfig: ApplicationConfig -) : EmojiService { - override suspend fun fetchEmoji(url: String): Pair { - val emoji = apResourceResolveServiceImpl.resolve(url, null as Long?) - return fetchEmoji(emoji) - } - - override suspend fun fetchEmoji(emoji: Emoji): Pair = emoji to save(emoji) - - private suspend fun save(emoji: Emoji): CustomEmoji { - val domain = URL(emoji.id).host - val name = emoji.name.trim(':') - val customEmoji = customEmojiRepository.findByNameAndDomain(name, domain) - - if (customEmoji != null) { - return customEmoji - } - - val instance = instanceService.fetchInstance(emoji.id) - - val media = mediaService.uploadRemoteMedia( - RemoteMedia( - emoji.name, - emoji.icon.url, - emoji.icon.mediaType.orEmpty(), - null - ) - ) - - val customEmoji1 = CustomEmoji( - id = customEmojiRepository.generateId(), - name = name, - domain = domain, - instanceId = instance.id, - url = media.url, - category = null, - createdAt = Instant.now() - ) - - return customEmojiRepository.save(customEmoji1) - } - - override suspend fun findByEmojiName(emojiName: String): CustomEmoji? { - val split = emojiName.trim(':').split("@") - - return when (split.size) { - 1 -> { - customEmojiRepository.findByNameAndDomain(split.first(), applicationConfig.url.host) - } - - 2 -> { - customEmojiRepository.findByNameAndDomain(split.first(), split[1]) - } - - else -> throw IllegalArgumentException("Unknown Emoji Format. $emojiName") - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt deleted file mode 100644 index ea9358a3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 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.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.query.AnnounceQueryService -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.media.RemoteMedia -import dev.usbharu.hideout.core.service.post.PostService -import io.ktor.client.plugins.* -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -interface APNoteService { - suspend fun fetchNote(url: String, targetActor: String? = null): Note = fetchNoteWithEntity(url, targetActor).first - suspend fun fetchNote(note: Note, targetActor: String? = null): Note - suspend fun fetchNoteWithEntity(url: String, targetActor: String? = null): Pair - - suspend fun fetchAnnounce(url: String, signerId: Long? = null): Pair - suspend fun fetchAnnounce(announce: Announce, signerId: Long? = null): Pair -} - -@Service -@Suppress("LongParameterList") -class APNoteServiceImpl( - private val postRepository: PostRepository, - private val apUserService: APUserService, - private val postService: PostService, - private val apResourceResolveService: APResourceResolveService, - private val postBuilder: Post.PostBuilder, - private val noteQueryService: NoteQueryService, - private val mediaService: MediaService, - private val emojiService: EmojiService, - private val announceQueryService: AnnounceQueryService - -) : APNoteService { - - private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java) - - override suspend fun fetchNoteWithEntity(url: String, targetActor: String?): Pair { - logger.debug("START Fetch Note url: {}", url) - - val post = noteQueryService.findByApid(url) - - if (post != null) { - logger.debug("SUCCESS Found in local url: {}", url) - return post - } - - logger.info("AP GET url: {}", url) - val note = try { - apResourceResolveService.resolve(url, null as Long?) - } catch (e: ClientRequestException) { - logger.warn( - "FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}", - e.response.status, - url - ) - throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e) - } - val savedNote = saveIfMissing(note, targetActor) - logger.debug("SUCCESS Fetch Note url: {}", url) - return savedNote - } - - override suspend fun fetchAnnounce(url: String, signerId: Long?): Pair { - logger.debug("START Fetch Announce url: {}", url) - - val post: Pair? = announceQueryService.findByApId(url) - - if (post != null) { - logger.debug("SUCCESS Found in local url: {}", url) - return post - } - - logger.info("AP GET url: {}", url) - - val announce = try { - apResourceResolveService.resolve(url, signerId) - } catch (e: ClientRequestException) { - logger.warn( - "FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}", - e.response.status, - url - ) - throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e) - } - - return fetchAnnounce(announce, signerId) - } - - override suspend fun fetchAnnounce(announce: Announce, signerId: Long?): Pair { - val findByApId = announceQueryService.findByApId(announce.id) - - if (findByApId != null) { - return findByApId - } - - val (_, actor) = apUserService.fetchPersonWithEntity(announce.actor, null) - - val (_, post) = fetchNoteWithEntity(announce.apObject, null) - - val visibility = if (announce.to.contains(public)) { - Visibility.PUBLIC - } else if (announce.to.contains(actor.followers) && announce.cc.contains(public)) { - Visibility.UNLISTED - } else if (announce.to.contains(actor.followers)) { - Visibility.FOLLOWERS - } else { - Visibility.DIRECT - } - - val createRemote = postService.createRemote( - postBuilder.pureRepostOf( - id = postRepository.generateId(), - actorId = actor.id, - visibility = visibility, - createdAt = Instant.parse(announce.published), - url = announce.id, - repost = post, - apId = announce.id - ) - ) - return announce to createRemote - } - - private suspend fun saveIfMissing( - note: Note, - targetActor: String? - ): Pair = noteQueryService.findByApid(note.id) ?: saveNote(note, targetActor) - - private suspend fun saveNote(note: Note, targetActor: String?): Pair { - val person = apUserService.fetchPersonWithEntity( - note.attributedTo, - targetActor - ) - - val post = postRepository.findByApId(note.id) - if (post != null) { - return note to post - } - - logger.debug("VISIBILITY url: {} to: {} cc: {}", note.id, note.to, note.cc) - val visibility = visibility(note, person) - logger.debug("VISIBILITY is {} url: {}", visibility.name, note.id) - - val reply = note.inReplyTo?.let { - fetchNote(it, targetActor) - postRepository.findByUrl(it) - } - - val quote = (note.misskeyQuote ?: note.quoteUri ?: note.quoteUrl)?.let { - fetchNote(it, targetActor) - postRepository.findByUrl(it) - } - - val emojis = buildEmojis(note) - - val mediaList = note.attachment.map { - mediaService.uploadRemoteMedia( - RemoteMedia( - it.name, - it.url, - it.mediaType, - description = it.name - ) - ) - }.map { it.id } - - val createPost = - post(quote, person, note, visibility, reply, mediaList, emojis) - - val createRemote = postService.createRemote(createPost) - return note to createRemote - } - - private suspend fun post( - quote: Post?, - person: Pair, - note: Note, - visibility: Visibility, - reply: Post?, - mediaList: List, - emojis: List - ) = if (quote != null) { - postBuilder.quoteRepostOf( - id = postRepository.generateId(), - actorId = person.second.id, - content = note.content, - createdAt = Instant.parse(note.published), - visibility = visibility, - url = note.id, - replyId = reply?.id, - sensitive = note.sensitive, - apId = note.id, - mediaIds = mediaList, - emojiIds = emojis, - repost = quote - ) - } else { - postBuilder.of( - id = postRepository.generateId(), - actorId = person.second.id, - content = note.content, - createdAt = Instant.parse(note.published).toEpochMilli(), - visibility = visibility, - url = note.id, - replyId = reply?.id, - sensitive = note.sensitive, - apId = note.id, - mediaIds = mediaList, - emojiIds = emojis - ) - } - - private suspend fun buildEmojis(note: Note) = note.tag - .filterIsInstance() - .map { - emojiService.fetchEmoji(it).second - } - .map { - it.id - } - - private fun visibility( - note: Note, - person: Pair - ): Visibility { - val visibility = if (note.to.contains(public)) { - Visibility.PUBLIC - } else if (note.to.contains(person.second.followers) && note.cc.contains(public)) { - Visibility.UNLISTED - } else if (note.to.contains(person.second.followers)) { - Visibility.FOLLOWERS - } else { - Visibility.DIRECT - } - return visibility - } - - override suspend fun fetchNote(note: Note, targetActor: String?): Note = - saveIfMissing(note, targetActor).first - - companion object { - const val public: String = "https://www.w3.org/ns/activitystreams#Public" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt deleted file mode 100644 index df9ba955..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.query.FollowerQueryService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class NoteApApiServiceImpl( - private val noteQueryService: NoteQueryService, - private val followerQueryService: FollowerQueryService, - private val transaction: Transaction -) : NoteApApiService { - override suspend fun getNote(postId: Long, userId: Long?): Note? = transaction.transaction { - val findById = noteQueryService.findById(postId) - - if (findById == null) { - logger.warn("Note not found. $postId $userId") - return@transaction null - } - - when (findById.second.visibility) { - Visibility.PUBLIC, Visibility.UNLISTED -> { - return@transaction findById.first - } - - Visibility.FOLLOWERS -> { - return@transaction getFollowersNote(userId, findById) - } - - Visibility.DIRECT -> return@transaction null - } - } - - private suspend fun getFollowersNote( - userId: Long?, - findById: Pair - ): Note? { - if (userId == null) { - return null - } - - if (followerQueryService.alreadyFollow(findById.second.actorId, userId)) { - return findById.first - } - return null - } - - companion object { - private val logger = LoggerFactory.getLogger(NoteApApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt deleted file mode 100644 index 336a4de5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.activitypub.service.objects.user - -import dev.usbharu.hideout.activitypub.domain.model.Image -import dev.usbharu.hideout.activitypub.domain.model.Key -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.user.RemoteUserCreateDto -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -interface APUserService { - suspend fun getPersonByName(name: String): Person - - /** - * Fetch person - * - * @param url - * @param targetActor 署名するユーザー - * @return - */ - suspend fun fetchPerson(url: String, targetActor: String? = null, idOverride: Long? = null): Person - - suspend fun fetchPersonWithEntity( - url: String, - targetActor: String? = null, - idOverride: Long? = null, - ): Pair -} - -@Service -class APUserServiceImpl( - private val userService: UserService, - private val transaction: Transaction, - private val applicationConfig: ApplicationConfig, - private val apResourceResolveService: APResourceResolveService, - private val actorRepository: ActorRepository, -) : - APUserService { - - override suspend fun getPersonByName(name: String): Person { - val userEntity = transaction.transaction { - actorRepository.findByNameAndDomain(name, applicationConfig.url.host) - ?: throw UserNotFoundException.withNameAndDomain(name, applicationConfig.url.host) - } - // TODO: JOINで書き直し - val userUrl = "${applicationConfig.url}/users/$name" - return Person( - type = emptyList(), - name = userEntity.name, - id = userUrl, - preferredUsername = name, - summary = userEntity.description, - inbox = "$userUrl/inbox", - outbox = "$userUrl/outbox", - url = userUrl, - icon = Image( - type = emptyList(), - mediaType = "image/jpeg", - url = "$userUrl/icon.jpg" - ), - publicKey = Key( - id = userEntity.keyId, - owner = userUrl, - publicKeyPem = userEntity.publicKey - ), - endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), - followers = userEntity.followers, - following = userEntity.following, - manuallyApprovesFollowers = userEntity.locked - ) - } - - override suspend fun fetchPerson(url: String, targetActor: String?, idOverride: Long?): Person = - fetchPersonWithEntity(url, targetActor, idOverride).first - - override suspend fun fetchPersonWithEntity( - url: String, - targetActor: String?, - idOverride: Long?, - ): Pair { - val userEntity = actorRepository.findByUrl(url) - - if (userEntity != null && idOverride == null) { - return entityToPerson(userEntity, userEntity.url) to userEntity - } - - val person = apResourceResolveService.resolve(url, null as Long?) - - val id = person.id - - val actor = actorRepository.findByUrlWithLock(id) - - if (actor != null && idOverride == null) { - return person to actor - } - - return person to userService.createRemoteUser( - RemoteUserCreateDto( - name = person.preferredUsername, - domain = id.substringAfter("://").substringBefore("/"), - screenName = person.name ?: person.preferredUsername, - description = person.summary.orEmpty(), - inbox = person.inbox, - outbox = person.outbox, - url = id, - publicKey = person.publicKey.publicKeyPem, - keyId = person.publicKey.id, - following = person.following, - followers = person.followers, - sharedInbox = person.endpoints["sharedInbox"], - locked = person.manuallyApprovesFollowers - ), - idOverride - ) - } - - private fun entityToPerson( - actorEntity: Actor, - id: String, - ) = Person( - type = emptyList(), - name = actorEntity.name, - id = id, - preferredUsername = actorEntity.name, - summary = actorEntity.description, - inbox = "$id/inbox", - outbox = "$id/outbox", - url = id, - icon = Image( - type = emptyList(), - mediaType = "image/jpeg", - url = "$id/icon.jpg" - ), - publicKey = Key( - id = actorEntity.keyId, - owner = id, - publicKeyPem = actorEntity.publicKey - ), - endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), - followers = actorEntity.followers, - following = actorEntity.following, - manuallyApprovesFollowers = actorEntity.locked - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt deleted file mode 100644 index afe658a1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/ActivityPubConfig.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.application.config - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.activitypub.domain.model.StringORObjectSerializer -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject -import dev.usbharu.hideout.core.infrastructure.httpsignature.HttpRequestMixIn -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.sign.HttpSignatureSigner -import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import java.time.format.DateTimeFormatter -import java.util.* - -@Configuration -class ActivityPubConfig { - - @Bean - @Qualifier("activitypub") - fun objectMapper(): ObjectMapper { - val module = SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer()) - - val objectMapper = jacksonObjectMapper() - .registerModules(module) - .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) - .setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP)) - .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SKIP)) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(JsonParser.Feature.ALLOW_COMMENTS, true) - .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) - .configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true) - .addMixIn(HttpRequest::class.java, HttpRequestMixIn::class.java) - - return objectMapper - } - - @Bean - @Qualifier("http") - fun dateTimeFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - - @Bean - fun httpSignatureSigner(): HttpSignatureSigner = RsaSha256HttpSignatureSigner() -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/CaptchaConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/CaptchaConfig.kt deleted file mode 100644 index ac8237f6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/CaptchaConfig.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.usbharu.hideout.application.config - -import org.springframework.boot.context.properties.ConfigurationProperties - -@ConfigurationProperties("hideout.security") -data class CaptchaConfig( - val reCaptchaSiteKey: String?, - val reCaptchaSecretKey: String? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt deleted file mode 100644 index 2f783282..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpClientConfig.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.application.config - -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.cache.* -import io.ktor.client.plugins.logging.* -import org.springframework.boot.info.BuildProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class HttpClientConfig { - @Bean - fun httpClient(buildProperties: BuildProperties, applicationConfig: ApplicationConfig): HttpClient = - HttpClient(CIO).config { - install(Logging) { - logger = Logger.DEFAULT - level = LogLevel.ALL - } - install(HttpCache) { - } - expectSuccess = true - install(UserAgent) { - agent = "Hideout/${buildProperties.version} (${applicationConfig.url})" - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt deleted file mode 100644 index 9935c53b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HttpSignatureConfig.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.application.config - -import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner -import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser -import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier -import dev.usbharu.httpsignature.verify.SignatureHeaderParser -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class HttpSignatureConfig { - @Bean - fun defaultSignatureHeaderParser(): DefaultSignatureHeaderParser = DefaultSignatureHeaderParser() - - @Bean - fun rsaSha256HttpSignatureVerifier( - signatureHeaderParser: SignatureHeaderParser, - signatureSigner: RsaSha256HttpSignatureSigner - ): RsaSha256HttpSignatureVerifier = RsaSha256HttpSignatureVerifier(signatureHeaderParser, signatureSigner) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MdcXrequestIdFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MdcXrequestIdFilter.kt deleted file mode 100644 index 188251de..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MdcXrequestIdFilter.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.application.config - -import jakarta.servlet.Filter -import jakarta.servlet.FilterChain -import jakarta.servlet.ServletRequest -import jakarta.servlet.ServletResponse -import org.slf4j.MDC -import org.springframework.boot.autoconfigure.security.SecurityProperties -import org.springframework.core.annotation.Order -import org.springframework.stereotype.Component -import java.util.* - -@Component -@Order(SecurityProperties.DEFAULT_FILTER_ORDER - 1) -class MdcXrequestIdFilter : Filter { - override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain) { - val uuid = UUID.randomUUID() - try { - MDC.put(KEY, uuid.toString()) - chain.doFilter(request, response) - } finally { - MDC.remove(KEY) - } - } - - companion object { - private const val KEY = "x-request-id" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/OwlConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/OwlConfig.kt deleted file mode 100644 index e5454e23..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/OwlConfig.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.application.config - -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.owl.broker.ModuleContext -import dev.usbharu.owl.common.property.* -import dev.usbharu.owl.common.retry.RetryPolicyFactory -import dev.usbharu.owl.producer.api.OWL -import dev.usbharu.owl.producer.api.OwlProducer -import dev.usbharu.owl.producer.defaultimpl.DEFAULT -import dev.usbharu.owl.producer.embedded.EMBEDDED -import dev.usbharu.owl.producer.embedded.EMBEDDED_GRPC -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import java.util.* - -@Configuration -class OwlConfig(private val producerConfig: ProducerConfig) { - @Bean - fun producer( - @Autowired(required = false) retryPolicyFactory: RetryPolicyFactory? = null, - @Qualifier("activitypub") objectMapper: ObjectMapper, - ): OwlProducer { - return when (producerConfig.mode) { - ProducerMode.EMBEDDED -> { - OWL(EMBEDDED) { - if (retryPolicyFactory != null) { - this.retryPolicyFactory = retryPolicyFactory - } - if (producerConfig.port != null) { - this.port = producerConfig.port.toString() - } - val moduleContext = ServiceLoader.load(ModuleContext::class.java).firstOrNull() - if (moduleContext != null) { - this.moduleContext = moduleContext - } - this.propertySerializerFactory = CustomPropertySerializerFactory( - setOf( - IntegerPropertySerializer(), - StringPropertyValueSerializer(), - DoublePropertySerializer(), - BooleanPropertySerializer(), - LongPropertySerializer(), - FloatPropertySerializer(), - ObjectPropertySerializer(objectMapper), - ) - ) - } - } - - ProducerMode.GRPC -> { - OWL(EMBEDDED_GRPC) { - } - } - - ProducerMode.EMBEDDED_GRPC -> { - OWL(DEFAULT) { - } - } - } - } -} - -@ConfigurationProperties("hideout.owl.producer") -data class ProducerConfig(val mode: ProducerMode = ProducerMode.EMBEDDED, val port: Int? = null) - -enum class ProducerMode { - GRPC, - EMBEDDED, - EMBEDDED_GRPC -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt deleted file mode 100644 index e34fc87c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ /dev/null @@ -1,413 +0,0 @@ -/* - * 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.application.config - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.module.SimpleModule -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.activitypub.domain.model.StringORObjectSerializer -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureFilter -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUserDetailsService -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureVerifierComposite -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsServiceImpl -import dev.usbharu.hideout.util.RsaUtil -import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner -import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser -import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier -import jakarta.annotation.PostConstruct -import jakarta.servlet.* -import org.springframework.beans.factory.support.BeanDefinitionRegistry -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary -import org.springframework.core.annotation.Order -import org.springframework.http.HttpMethod.GET -import org.springframework.http.HttpMethod.POST -import org.springframework.http.HttpStatus -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.security.authentication.AccountStatusUserDetailsChecker -import org.springframework.security.authentication.AuthenticationManager -import org.springframework.security.authentication.dao.DaoAuthenticationProvider -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration -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.config.http.SessionCreationPolicy -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolderStrategy -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.jwt.JwtDecoder -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType -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.FilterChainProxy -import org.springframework.security.web.SecurityFilterChain -import org.springframework.security.web.access.ExceptionTranslationFilter -import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler -import org.springframework.security.web.authentication.HttpStatusEntryPoint -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer -import org.springframework.security.web.debug.DebugFilter -import org.springframework.security.web.firewall.HttpFirewall -import org.springframework.security.web.firewall.RequestRejectedHandler -import org.springframework.security.web.savedrequest.RequestCacheAwareFilter -import org.springframework.security.web.util.matcher.AnyRequestMatcher -import org.springframework.web.filter.CompositeFilter -import java.io.IOException -import java.security.KeyPairGenerator -import java.security.interfaces.RSAPrivateKey -import java.security.interfaces.RSAPublicKey -import java.util.* - -@EnableWebSecurity(debug = false) -@Configuration -@Suppress("FunctionMaxLength", "TooManyFunctions", "LongMethod") -class SecurityConfig { - - @Bean - fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? = - authenticationConfiguration.authenticationManager - - @Bean - @Order(1) - fun httpSignatureFilterChain( - http: HttpSecurity, - httpSignatureFilter: HttpSignatureFilter, - ): SecurityFilterChain { - http { - securityMatcher("/users/*/posts/*") - addFilterAt(httpSignatureFilter) - addFilterBefore( - ExceptionTranslationFilter(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) - ) - authorizeHttpRequests { - authorize(anyRequest, permitAll) - } - exceptionHandling { - authenticationEntryPoint = HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED) - defaultAuthenticationEntryPointFor( - HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), - AnyRequestMatcher.INSTANCE - ) - } - sessionManagement { - sessionCreationPolicy = SessionCreationPolicy.STATELESS - } - } - return http.build() - } - - @Bean - fun getHttpSignatureFilter( - authenticationManager: AuthenticationManager, - httpSignatureHeaderChecker: HttpSignatureHeaderChecker, - ): HttpSignatureFilter { - val httpSignatureFilter = - HttpSignatureFilter(DefaultSignatureHeaderParser(), httpSignatureHeaderChecker) - httpSignatureFilter.setAuthenticationManager(authenticationManager) - httpSignatureFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false) - val authenticationEntryPointFailureHandler = - AuthenticationEntryPointFailureHandler(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) - authenticationEntryPointFailureHandler.setRethrowAuthenticationServiceException(false) - httpSignatureFilter.setAuthenticationFailureHandler(authenticationEntryPointFailureHandler) - return httpSignatureFilter - } - - @Bean - @Order(2) - fun daoAuthenticationProvider(userDetailsServiceImpl: UserDetailsServiceImpl): DaoAuthenticationProvider { - val daoAuthenticationProvider = DaoAuthenticationProvider() - daoAuthenticationProvider.setUserDetailsService(userDetailsServiceImpl) - - return daoAuthenticationProvider - } - - @Bean - @Order(1) - fun httpSignatureAuthenticationProvider( - transaction: Transaction, - actorRepository: ActorRepository, - ): PreAuthenticatedAuthenticationProvider { - val provider = PreAuthenticatedAuthenticationProvider() - val signatureHeaderParser = DefaultSignatureHeaderParser() - provider.setPreAuthenticatedUserDetailsService( - HttpSignatureUserDetailsService( - HttpSignatureVerifierComposite( - mapOf( - "rsa-sha256" to RsaSha256HttpSignatureVerifier( - signatureHeaderParser, RsaSha256HttpSignatureSigner() - ) - ), - signatureHeaderParser - ), - transaction, - signatureHeaderParser, - actorRepository - ) - ) - provider.setUserDetailsChecker(AccountStatusUserDetailsChecker()) - return provider - } - - @Bean - @Order(2) - fun oauth2SecurityFilterChain(http: HttpSecurity): SecurityFilterChain { - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) - http { - exceptionHandling { - authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/login") - } - oauth2ResourceServer { - jwt { - } - } - } - return http.build() - } - - @Bean - @Order(5) - fun defaultSecurityFilterChain( - http: HttpSecurity, - ): SecurityFilterChain { - http { - authorizeHttpRequests { - authorize("/error", permitAll) - authorize("/login", permitAll) - authorize(GET, "/.well-known/**", permitAll) - authorize(GET, "/nodeinfo/2.0", permitAll) - - authorize(POST, "/inbox", permitAll) - authorize(POST, "/users/*/inbox", permitAll) - authorize(GET, "/users/*", permitAll) - authorize(GET, "/users/*/posts/*", permitAll) - - authorize("/dev/usbharu/hideout/core/service/auth/sign_up", hasRole("ANONYMOUS")) - authorize(GET, "/files/*", permitAll) - authorize(GET, "/users/*/icon.jpg", permitAll) - authorize(GET, "/users/*/header.jpg", permitAll) - - authorize(anyRequest, authenticated) - } - - oauth2ResourceServer { - jwt { } - } - - formLogin { - } - - csrf { - ignoringRequestMatchers("/users/*/inbox", "/inbox", "/api/v1/apps") - } - - headers { - frameOptions { - sameOrigin = true - } - } - } - return http.build() - } - - @Bean - fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() - - @Bean - @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "false", matchIfMissing = true) - fun genJwkSource(): JWKSource { - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(2048) - val generateKeyPair = keyPairGenerator.generateKeyPair() - val rsaPublicKey = generateKeyPair.public as RSAPublicKey - val rsaPrivateKey = generateKeyPair.private as RSAPrivateKey - val rsaKey = RSAKey.Builder(rsaPublicKey).privateKey(rsaPrivateKey).keyID(UUID.randomUUID().toString()).build() - - val jwkSet = JWKSet(rsaKey) - return ImmutableJWKSet(jwkSet) - } - - @Bean - @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") - fun loadJwkSource(jwkConfig: JwkConfig): JWKSource { - val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey)) - .privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey)).keyID(jwkConfig.keyId).build() - return ImmutableJWKSet(JWKSet(rsaKey)) - } - - @Bean - fun jwtDecoder(jwkSource: JWKSource): JwtDecoder = - OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource) - - @Bean - fun authorizationServerSettings(): AuthorizationServerSettings { - return AuthorizationServerSettings.builder().authorizationEndpoint("/oauth/authorize") - .tokenEndpoint("/oauth/token").tokenRevocationEndpoint("/oauth/revoke").build() - } - - @Bean - fun jwtTokenCustomizer(): OAuth2TokenCustomizer { - return OAuth2TokenCustomizer { context: JwtEncodingContext -> - - if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType && - context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE - ) { - val userDetailsImpl = context.getPrincipal().principal as UserDetailsImpl - context.claims.claim("uid", userDetailsImpl.id.toString()) - } - } - } - - @Bean - @Primary - fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer { - return Jackson2ObjectMapperBuilderCustomizer { - it.serializationInclusion(JsonInclude.Include.ALWAYS) - .modulesToInstall(SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer())) - .serializers() - } - } - - @Bean - fun mappingJackson2HttpMessageConverter(): MappingJackson2HttpMessageConverter { - val builder = Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL) - builder.modulesToInstall(SimpleModule().addSerializer(StringOrObject::class.java, StringORObjectSerializer())) - return MappingJackson2HttpMessageConverter(builder.build()) - } - - // Spring Security 3.2.1 に存在する EnableWebSecurity(debug = true)にすると発生するエラーに対処するためのコード - // trueにしないときはコメントアウト - - // @Bean - fun beanDefinitionRegistryPostProcessor(): BeanDefinitionRegistryPostProcessor { - return BeanDefinitionRegistryPostProcessor { registry: BeanDefinitionRegistry -> - registry.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME).beanClassName = - CompositeFilterChainProxy::class.java.name - } - } - - @Suppress("ExpressionBodySyntax") - internal class CompositeFilterChainProxy(filters: List) : FilterChainProxy() { - private val doFilterDelegate: Filter - - private val springSecurityFilterChain: FilterChainProxy - - init { - this.doFilterDelegate = createDoFilterDelegate(filters) - this.springSecurityFilterChain = findFilterChainProxy(filters) - } - - override fun afterPropertiesSet() { - springSecurityFilterChain.afterPropertiesSet() - } - - @Throws(IOException::class, ServletException::class) - override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { - doFilterDelegate.doFilter(request, response, chain) - } - - override fun getFilters(url: String): List { - return springSecurityFilterChain.getFilters(url) - } - - override fun getFilterChains(): List { - return springSecurityFilterChain.filterChains - } - - override fun setSecurityContextHolderStrategy(securityContextHolderStrategy: SecurityContextHolderStrategy) { - springSecurityFilterChain.setSecurityContextHolderStrategy(securityContextHolderStrategy) - } - - override fun setFilterChainValidator(filterChainValidator: FilterChainValidator) { - springSecurityFilterChain.setFilterChainValidator(filterChainValidator) - } - - override fun setFilterChainDecorator(filterChainDecorator: FilterChainDecorator) { - springSecurityFilterChain.setFilterChainDecorator(filterChainDecorator) - } - - override fun setFirewall(firewall: HttpFirewall) { - springSecurityFilterChain.setFirewall(firewall) - } - - override fun setRequestRejectedHandler(requestRejectedHandler: RequestRejectedHandler) { - springSecurityFilterChain.setRequestRejectedHandler(requestRejectedHandler) - } - - companion object { - private fun createDoFilterDelegate(filters: List): Filter { - val delegate: CompositeFilter = CompositeFilter() - delegate.setFilters(filters) - return delegate - } - - private fun findFilterChainProxy(filters: List): FilterChainProxy { - for (filter in filters) { - if (filter is FilterChainProxy) { - return filter - } - if (filter is DebugFilter) { - return filter.filterChainProxy - } - } - throw IllegalStateException("Couldn't find FilterChainProxy in $filters") - } - } - } -} - -@ConfigurationProperties("hideout.security.jwt") -@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") -data class JwkConfig( - val keyId: String, - val publicKey: String, - val privateKey: String, -) - -@Configuration -class PostSecurityConfig( - val auth: AuthenticationManagerBuilder, - val daoAuthenticationProvider: DaoAuthenticationProvider, - val httpSignatureAuthenticationProvider: PreAuthenticatedAuthenticationProvider, -) { - - @PostConstruct - fun config() { - auth.authenticationProvider(daoAuthenticationProvider) - auth.authenticationProvider(httpSignatureAuthenticationProvider) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt deleted file mode 100644 index 8f1b97ce..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.application.config - -import org.springframework.beans.factory.annotation.Autowired -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 org.springframework.web.filter.CommonsRequestLoggingFilter -import java.net.URL - -@Configuration -class SpringConfig { - - @Autowired - lateinit var config: ApplicationConfig - - @Bean - fun requestLoggingFilter(): CommonsRequestLoggingFilter { - val loggingFilter = CommonsRequestLoggingFilter() - loggingFilter.setIncludeHeaders(true) - loggingFilter.setIncludeClientInfo(true) - loggingFilter.setIncludeQueryString(true) - loggingFilter.setIncludePayload(true) - loggingFilter.setMaxPayloadLength(64000) - return loggingFilter - } -} - -@ConfigurationProperties("hideout") -data class ApplicationConfig( - val url: URL, - val private: Boolean = true, -) - -@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 -) - -/** - * メディアの保存にローカルファイルシステムを使用する際のコンフィグ - * - * @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? -) - -@ConfigurationProperties("hideout.character-limit") -data class CharacterLimit( - val general: General = General(), - val post: Post = Post(), - val account: Account = Account(), - val instance: Instance = Instance() -) { - - data class General( - val url: Int = 1000, - val domain: Int = 1000, - val publicKey: Int = 10000, - val privateKey: Int = 10000 - ) - - data class Post( - val text: Int = 3000, - val overview: Int = 3000 - ) - - data class Account( - val id: Int = 300, - val name: Int = 300, - val description: Int = 10000 - ) - - data class Instance( - val name: Int = 600, - val description: Int = 10000 - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/OwlProducerRunner.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/OwlProducerRunner.kt deleted file mode 100644 index ca86bce1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/OwlProducerRunner.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.application.external - -import dev.usbharu.owl.common.task.TaskDefinition -import dev.usbharu.owl.producer.api.OwlProducer -import kotlinx.coroutines.runBlocking -import org.springframework.beans.factory.DisposableBean -import org.springframework.boot.ApplicationArguments -import org.springframework.boot.ApplicationRunner -import org.springframework.stereotype.Component - -@Component -class OwlProducerRunner(private val owlProducer: OwlProducer, private val taskDefinitions: List>) : - ApplicationRunner, DisposableBean { - override fun run(args: ApplicationArguments?) { - runBlocking { - owlProducer.start() - taskDefinitions.forEach { taskDefinition -> owlProducer.registerTask(taskDefinition) } - } - } - - override fun destroy() { - System.err.println("destroy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") - runBlocking { - owlProducer.stop() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt deleted file mode 100644 index 1f8dad8a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -import org.jetbrains.exposed.sql.* - -fun Query.withPagination(page: Page, exp: ExpressionWithColumnType): PaginationList { - page.limit?.let { limit(it) } - val resultRows = if (page.minId != null) { - page.maxId?.let { it: Long -> andWhere { exp.less(it) } } - andWhere { exp.greater(page.minId!!) } - reversed() - } else { - page.maxId?.let { andWhere { exp.less(it) } } - page.sinceId?.let { andWhere { exp.greater(it) } } - orderBy(exp, SortOrder.DESC) - toList() - } - - return PaginationList(resultRows, resultRows.firstOrNull()?.getOrNull(exp), resultRows.lastOrNull()?.getOrNull(exp)) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/Page.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/Page.kt deleted file mode 100644 index c6178261..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/Page.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -sealed class Page { - abstract val maxId: Long? - abstract val sinceId: Long? - abstract val minId: Long? - abstract val limit: Int? - - data class PageByMaxId( - override val maxId: Long?, - override val sinceId: Long?, - override val limit: Int? - ) : Page() { - override val minId: Long? = null - } - - data class PageByMinId( - override val maxId: Long?, - override val minId: Long?, - override val limit: Int? - ) : Page() { - override val sinceId: Long? = null - } - - companion object { - @Suppress("FunctionMinLength") - fun of( - maxId: Long? = null, - sinceId: Long? = null, - minId: Long? = null, - limit: Int? = null - ): Page = - if (minId != null) { - PageByMinId( - maxId, - minId, - limit - ) - } else { - PageByMaxId( - maxId, - sinceId, - limit - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationList.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationList.kt deleted file mode 100644 index d796e48c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationList.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -class PaginationList(list: List, val next: ID?, val prev: ID?) : List by list - -fun PaginationList.toHttpHeader( - nextBlock: (string: String) -> String, - prevBlock: (string: String) -> String -): String? { - val mutableListOf = mutableListOf() - if (next != null) { - mutableListOf.add("<${nextBlock(this.next.toString())}>; rel=\"next\"") - } - if (prev != null) { - mutableListOf.add("<${prevBlock(this.prev.toString())}>; rel=\"prev\"") - } - - if (mutableListOf.isEmpty()) { - return null - } - - return mutableListOf.joinToString(", ") -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImpl.kt deleted file mode 100644 index b801ba1e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImpl.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.application.service.init - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.NotInitException -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.Meta -import dev.usbharu.hideout.core.domain.model.meta.MetaRepository -import org.springframework.stereotype.Service - -@Service -class MetaServiceImpl(private val metaRepository: MetaRepository, private val transaction: Transaction) : - MetaService { - override suspend fun getMeta(): Meta = - transaction.transaction { metaRepository.get() ?: throw NotInitException("Meta is null") } - - override suspend fun updateMeta(meta: Meta): Unit = transaction.transaction { - metaRepository.save(meta) - } - - override suspend fun getJwtMeta(): Jwt = getMeta().jwt -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseService.kt deleted file mode 100644 index ffec5686..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseService.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.application.service.init - -import org.springframework.stereotype.Service - -@Service -interface ServerInitialiseService { - suspend fun init() -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImpl.kt deleted file mode 100644 index 323b78d7..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImpl.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.application.service.init - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.Meta -import dev.usbharu.hideout.core.domain.model.meta.MetaRepository -import dev.usbharu.hideout.util.ServerUtil -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.security.KeyPairGenerator -import java.util.* - -@Service -class ServerInitialiseServiceImpl( - private val metaRepository: MetaRepository, - private val transaction: Transaction -) : - ServerInitialiseService { - - val logger: Logger = LoggerFactory.getLogger(ServerInitialiseServiceImpl::class.java) - - override suspend fun init() { - transaction.transaction { - val savedMeta = metaRepository.get() - val implementationVersion = ServerUtil.getImplementationVersion() - if (wasInitialised(savedMeta).not()) { - logger.info("Start Initialise") - initialise(implementationVersion) - logger.info("Finish Initialise") - return@transaction - } - - if (isVersionChanged(requireNotNull(savedMeta))) { - logger.info("Version changed!! (${savedMeta.version} -> $implementationVersion)") - updateVersion(savedMeta, implementationVersion) - } - } - } - - private fun wasInitialised(meta: Meta?): Boolean { - logger.debug("Initialise checking...") - return meta != null - } - - private fun isVersionChanged(meta: Meta): Boolean = meta.version != ServerUtil.getImplementationVersion() - - private suspend fun initialise(implementationVersion: String) { - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(2048) - val generateKeyPair = keyPairGenerator.generateKeyPair() - val jwt = Jwt( - UUID.randomUUID(), - Base64.getEncoder().encodeToString(generateKeyPair.private.encoded), - Base64.getEncoder().encodeToString(generateKeyPair.public.encoded) - ) - val meta = Meta(implementationVersion, jwt) - metaRepository.save(meta) - } - - private suspend fun updateVersion(meta: Meta, version: String) { - metaRepository.save(meta.copy(version = version)) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/DeleteLocalActorApplicationService.kt similarity index 50% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/DeleteLocalActorApplicationService.kt index 32a09a76..73ef9151 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/DeleteLocalActorApplicationService.kt @@ -14,31 +14,24 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.webfinger +package dev.usbharu.hideout.core.application.actor -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor +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 org.springframework.stereotype.Service @Service -interface WebFingerApiService { - suspend fun findByNameAndDomain(name: String, domain: String): Actor -} - -@Service -class WebFingerApiServiceImpl( +class DeleteLocalActorApplicationService( private val transaction: Transaction, - private val actorRepository: ActorRepository -) : - WebFingerApiService { - override suspend fun findByNameAndDomain(name: String, domain: String): Actor { - return transaction.transaction { - actorRepository.findByNameAndDomain(name, domain) ?: throw UserNotFoundException.withNameAndDomain( - name, - domain - ) + private val actorRepository: ActorRepository, +) { + suspend fun delete(actorId: Long, executor: ActorId) { + transaction.transaction { + val id = ActorId(actorId) + val findById = actorRepository.findById(id)!! + findById.delete() + actorRepository.delete(findById) } } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetail.kt similarity index 86% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetail.kt index b4043b5c..16ece3c9 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetail.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.model +package dev.usbharu.hideout.core.application.actor -interface HasId { - val id: String -} +data class GetUserDetail(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetailApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetailApplicationService.kt new file mode 100644 index 00000000..e3b3b3d8 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetUserDetailApplicationService.kt @@ -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.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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.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(transaction, Companion.logger) { + companion object { + val logger = LoggerFactory.getLogger(GetUserDetailApplicationService::class.java) + } + + override suspend fun internalExecute(command: GetUserDetail, executor: CommandExecutor): UserDetail { + val userDetail = userDetailRepository.findById(command.id) + ?: throw IllegalArgumentException("actor does not exist") + val actor = actorRepository.findById(userDetail.actorId)!! + + val emojis = customEmojiRepository.findByIds(actor.emojis.map { it.emojiId }) + + return UserDetail.of(actor, userDetail, emojis) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt new file mode 100644 index 00000000..6d147f4a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt @@ -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.actor + +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.service.actor.local.AccountMigrationCheck.* +import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorMigrationCheckDomainService +import org.springframework.stereotype.Service + +@Service +class MigrationLocalActorApplicationService( + private val transaction: Transaction, + private val actorRepository: ActorRepository, + private val localActorMigrationCheckDomainService: LocalActorMigrationCheckDomainService, +) { + suspend fun migration(from: Long, to: Long, executor: ActorId) { + transaction.transaction { + val fromActorId = ActorId(from) + val toActorId = ActorId(to) + + val fromActor = actorRepository.findById(fromActorId)!! + val toActor = actorRepository.findById(toActorId)!! + + val canAccountMigration = localActorMigrationCheckDomainService.canAccountMigration(fromActor, toActor) + when (canAccountMigration) { + is AlreadyMoved -> TODO() + is CanAccountMigration -> { + fromActor.moveTo = toActorId + actorRepository.save(fromActor) + } + + is CircularReferences -> TODO() + is SelfReferences -> TODO() + is AlsoKnownAsNotFound -> TODO() + } + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/FormattedPostContent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActor.kt similarity index 82% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/FormattedPostContent.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActor.kt index 4dd62908..b54f2504 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/FormattedPostContent.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActor.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.post +package dev.usbharu.hideout.core.application.actor -data class FormattedPostContent( - val html: String, - val content: String +data class RegisterLocalActor( + val name: String, + val password: String, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt new file mode 100644 index 00000000..2e172e83 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt @@ -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.actor + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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.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(transaction, Companion.logger) { + + override suspend fun internalExecute(command: RegisterLocalActor, executor: CommandExecutor): URI { + if (actorDomainService.usernameAlreadyUse(command.name)) { + // todo 適切な例外を考える + throw Exception("Username already exists") + } + val instance = instanceRepository.findByUrl(applicationConfig.url.toURI())!! + + 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), + ) + userDetailRepository.save(userDetail) + return actor.url + } + + companion object { + val logger = LoggerFactory.getLogger(RegisterLocalActorApplicationService::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SetAlsoKnownAsLocalActorApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SetAlsoKnownAsLocalActorApplicationService.kt new file mode 100644 index 00000000..3f676614 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SetAlsoKnownAsLocalActorApplicationService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.core.application.actor + +import org.springframework.stereotype.Service + +@Service +interface SetAlsoKnownAsLocalActorApplicationService { + suspend fun setAlsoKnownAs(actorId: Long, alsoKnownAs: List) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SuspendLocalActorApplicationService.kt similarity index 51% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SuspendLocalActorApplicationService.kt index ee69cca9..082208b3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/SuspendLocalActorApplicationService.kt @@ -14,16 +14,24 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.user +package dev.usbharu.hideout.core.application.actor +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 org.springframework.stereotype.Service -import java.security.KeyPair @Service -interface UserAuthService { - fun hash(password: String): String +class SuspendLocalActorApplicationService( + private val transaction: Transaction, + private val actorRepository: ActorRepository, +) { + suspend fun suspend(actorId: Long, executor: ActorId) { + transaction.transaction { + val id = ActorId(actorId) - suspend fun usernameAlreadyUse(username: String): Boolean - - suspend fun generateKeyPair(): KeyPair + val findById = actorRepository.findById(id)!! + findById.suspend = true + } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UnsuspendLocalActorApplicationService.kt similarity index 52% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UnsuspendLocalActorApplicationService.kt index a065a58d..baf0ab4b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UnsuspendLocalActorApplicationService.kt @@ -14,21 +14,23 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.timeline +package dev.usbharu.hideout.core.application.actor -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Status +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 org.springframework.stereotype.Service @Service -@Suppress("LongParameterList") -interface GenerateTimelineService { +class UnsuspendLocalActorApplicationService( + private val transaction: Transaction, + private val actorRepository: ActorRepository, +) { + suspend fun unsuspend(actorId: Long, executor: Long) { + transaction.transaction { + val findById = actorRepository.findById(ActorId(actorId))!! - suspend fun getTimeline( - forUserId: Long? = null, - localOnly: Boolean = false, - mediaOnly: Boolean = false, - page: Page - ): PaginationList + findById.suspend = false + } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UserDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UserDetail.kt new file mode 100644 index 00000000..5b363296 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/UserDetail.kt @@ -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.actor + +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, + 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, + ): dev.usbharu.hideout.core.application.actor.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 + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSaveRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplication.kt similarity index 75% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSaveRequest.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplication.kt index 7e04f027..f39b957f 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSaveRequest.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplication.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.application.application -import java.nio.file.Path +import java.net.URI -data class MediaSaveRequest( +data class RegisterApplication( val name: String, - val preffix: String, - val filePath: Path, - val thumbnailPath: Path? + val redirectUris: Set, + val useRefreshToken: Boolean, + val scopes: Set, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplicationApplicationService.kt similarity index 52% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplicationApplicationService.kt index 3127d169..104cd3e0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisterApplicationApplicationService.kt @@ -14,13 +14,16 @@ * limitations under the License. */ -package dev.usbharu.hideout.mastodon.service.app +package dev.usbharu.hideout.core.application.application -import dev.usbharu.hideout.application.external.Transaction +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.service.userdetail.PasswordEncoder +import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SecureTokenGenerator -import dev.usbharu.hideout.domain.mastodon.model.generated.Application -import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest -import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.oauth2.core.AuthorizationGrantType import org.springframework.security.oauth2.core.ClientAuthenticationMethod import org.springframework.security.oauth2.server.authorization.client.RegisteredClient @@ -29,57 +32,60 @@ import org.springframework.security.oauth2.server.authorization.settings.ClientS import org.springframework.security.oauth2.server.authorization.settings.TokenSettings import org.springframework.stereotype.Service import java.time.Duration -import java.util.* @Service -interface AppApiService { - suspend fun createApp(appsRequest: AppsRequest): Application -} - -@Service -class AppApiServiceImpl( - private val registeredClientRepository: RegisteredClientRepository, - private val secureTokenGenerator: SecureTokenGenerator, +class RegisterApplicationApplicationService( + private val idGenerateService: IdGenerateService, private val passwordEncoder: PasswordEncoder, - private val transaction: Transaction -) : AppApiService { - override suspend fun createApp(appsRequest: AppsRequest): Application { + private val secureTokenGenerator: SecureTokenGenerator, + private val registeredClientRepository: RegisteredClientRepository, + private val transaction: Transaction, + private val applicationRepository: ApplicationRepository, +) { + suspend fun register(registerApplication: RegisterApplication): RegisteredApplication { return transaction.transaction { - val id = UUID.randomUUID().toString() + val id = idGenerateService.generateId() val clientSecret = secureTokenGenerator.generate() - val registeredClient = RegisteredClient.withId(id) - .clientId(id) + val registeredClient = RegisteredClient + .withId(id.toString()) + .clientId(id.toString()) .clientSecret(passwordEncoder.encode(clientSecret)) - .clientName(appsRequest.clientName) + .clientName(registerApplication.name) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .redirectUri(appsRequest.redirectUris) - .tokenSettings( - TokenSettings.builder() - .accessTokenTimeToLive( - Duration.ofSeconds(31536000000) + .apply { + if (registerApplication.useRefreshToken) { + authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + } else { + tokenSettings( + TokenSettings + .builder() + .accessTokenTimeToLive(Duration.ofSeconds(31536000000)) + .build() ) - .build() - ) + } + } + .redirectUris { set -> + set.addAll(registerApplication.redirectUris.map { it.toString() }) + } .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) } + .scopes { it.addAll(registerApplication.scopes) } .build() registeredClientRepository.save(registeredClient) - Application( - name = appsRequest.clientName, - vapidKey = "invalid-vapid-key", - website = appsRequest.website, - clientId = id, + val application = Application(ApplicationId(id), ApplicationName(registerApplication.name)) + + applicationRepository.save(application) + RegisteredApplication( + id = id, + name = registerApplication.name, clientSecret = clientSecret, - redirectUri = appsRequest.redirectUris + clientId = id.toString(), + redirectUris = registerApplication.redirectUris ) } } - - private fun parseScope(string: String): Set = string.split(" ").toSet() } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisteredApplication.kt similarity index 75% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisteredApplication.kt index 0455435f..a5a18032 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/application/RegisteredApplication.kt @@ -14,15 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.deletedActor +package dev.usbharu.hideout.core.application.application -import java.time.Instant +import java.net.URI -data class DeletedActor( +data class RegisteredApplication( val id: Long, val name: String, - val domain: String, - val apiId: String, - val publicKey: String, - val deletedAt: Instant, + val redirectUris: Set, + val clientSecret: String, + val clientId: String, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt similarity index 86% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt index 14abb53e..52a2bf0a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/DeleteFilter.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.model +package dev.usbharu.hideout.core.application.filter -interface HasName { - val name: String -} +data class DeleteFilter(val filterId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt new file mode 100644 index 00000000..e482991a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/Filter.kt @@ -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.filter + +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, + val filterAction: FilterAction, + val filterKeywords: Set, +) { + companion object { + fun of(filter: Filter): dev.usbharu.hideout.core.application.filter.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() + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt similarity index 87% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterKeyword.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt index 6c2ebc39..bb58f6e6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterKeyword.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/FilterKeyword.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.filter +package dev.usbharu.hideout.core.application.filter import dev.usbharu.hideout.core.domain.model.filter.FilterMode data class FilterKeyword( + val id: Long, val keyword: String, - val mode: FilterMode + val filterMode: FilterMode, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt similarity index 85% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt index d04a3a7c..b9089ab0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/GetFilter.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.model +package dev.usbharu.hideout.core.application.filter -interface HasActor { - val actor: String -} +data class GetFilter(val filterId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt similarity index 61% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt index d97f9264..3fd9a35f 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/Notification.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilter.kt @@ -14,17 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.notification +package dev.usbharu.hideout.core.application.filter -import java.time.Instant +import dev.usbharu.hideout.core.domain.model.filter.FilterAction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext -data class Notification( - val id: Long, - val type: String, - val userId: Long, - val sourceActorId: Long?, - val postId: Long?, - val text: String?, - val reactionId: Long?, - val createdAt: Instant +data class RegisterFilter( + val filterName: String, + val filterContext: Set, + val filterAction: FilterAction, + val filterKeywords: Set, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt similarity index 81% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeyword.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt index d6457116..b6e3ed31 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeyword.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/RegisterFilterKeyword.kt @@ -14,13 +14,11 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.filterkeyword +package dev.usbharu.hideout.core.application.filter import dev.usbharu.hideout.core.domain.model.filter.FilterMode -data class FilterKeyword( - val id: Long, - val filterId: Long, +data class RegisterFilterKeyword( val keyword: String, - val mode: FilterMode + val filterMode: FilterMode, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt new file mode 100644 index 00000000..2e28d5d0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserDeleteFilterApplicationService.kt @@ -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.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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 org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserDeleteFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, + logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(UserDeleteFilterApplicationService::class.java) + } + + override suspend fun internalExecute(command: DeleteFilter, executor: CommandExecutor) { + val filter = filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw Exception("not found") + filterRepository.delete(filter) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt new file mode 100644 index 00000000..0ecf97f8 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt @@ -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.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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 org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserGetFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, + logger + ) { + override suspend fun internalExecute(command: GetFilter, executor: CommandExecutor): Filter { + val filter = filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw Exception("Not Found") + + return Filter.of(filter) + } + + companion object { + private val logger = LoggerFactory.getLogger(UserGetFilterApplicationService::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt new file mode 100644 index 00000000..ac7a1309 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserRegisterFilterApplicationService.kt @@ -0,0 +1,67 @@ +/* + * 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.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.filter.FilterKeyword +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +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, +) : + AbstractApplicationService( + transaction, + logger + ) { + + companion object { + private val logger = LoggerFactory.getLogger(UserRegisterFilterApplicationService::class.java) + } + + override suspend fun internalExecute(command: RegisterFilter, executor: CommandExecutor): Filter { + require(executor is UserDetailGettableCommandExecutor) + + val filter = dev.usbharu.hideout.core.domain.model.filter.Filter.create( + id = FilterId(idGenerateService.generateId()), + userDetailId = UserDetailId(executor.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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt new file mode 100644 index 00000000..77d2e41b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt @@ -0,0 +1,59 @@ +/* + * 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.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.toURI()) + + if (findByUrl == null) { + val instance = Instance( + id = InstanceId(idGenerateService.generateId()), + name = InstanceName(applicationConfig.url.host), + description = InstanceDescription(""), + url = applicationConfig.url.toURI(), + iconUrl = applicationConfig.url.toURI(), + sharedInbox = null, + software = InstanceSoftware("hideout"), + version = InstanceVersion(buildProperties.version), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.now(), + ) + instanceRepository.save(instance) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/Media.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/Media.kt new file mode 100644 index 00000000..5ff3e4a8 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/Media.kt @@ -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.media + +import dev.usbharu.hideout.core.domain.model.media.FileType +import dev.usbharu.hideout.core.domain.model.media.Media +import dev.usbharu.hideout.core.domain.model.media.MimeType +import java.net.URI + +data class Media( + val id: Long, + val name: String, + val url: URI, + val thumbprintURI: URI?, + val remoteURL: URI?, + val type: FileType, + val mimeType: MimeType, + val blurHash: String?, + val description: String? +) { + companion object { + fun of(media: Media): dev.usbharu.hideout.core.application.media.Media { + return Media( + id = media.id.id, + name = media.name.name, + url = media.url, + thumbprintURI = media.thumbnailUrl, + remoteURL = media.remoteUrl, + type = media.type, + mimeType = media.mimeType, + blurHash = media.blurHash?.hash, + description = media.description?.description + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMedia.kt similarity index 78% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMedia.kt index a23f0b10..4a8af8d3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMedia.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.application.media +import java.net.URI import java.nio.file.Path -interface RemoteMediaDownloadService { - suspend fun download(url: String): Path -} +data class UploadMedia(val path: Path, val name: String, val remoteUri: URI?, val description: String?) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMediaApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMediaApplicationService.kt new file mode 100644 index 00000000..e3ba618e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/media/UploadMediaApplicationService.kt @@ -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.media + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.media.* +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 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 +) : AbstractApplicationService( + transaction, + logger +) { + companion object { + private val logger = LoggerFactory.getLogger(UploadMediaApplicationService::class.java) + } + + override suspend fun internalExecute(command: UploadMedia, executor: CommandExecutor): Media { + 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) } + ) + + mediaRepository.save(media) + + return Media.of(media) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/DeleteLocalPostApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/DeleteLocalPostApplicationService.kt new file mode 100644 index 00000000..971f0f7b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/DeleteLocalPostApplicationService.kt @@ -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.application.post + +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.userdetails.UserDetailRepository +import org.springframework.stereotype.Service + +@Service +class DeleteLocalPostApplicationService( + private val postRepository: PostRepository, + private val userDetailRepository: UserDetailRepository, + private val actorRepository: ActorRepository, +) { + suspend fun delete(postId: Long, userDetailId: Long) { + val findById = postRepository.findById(PostId(postId))!! + val user = userDetailRepository.findById(userDetailId)!! + val actor = actorRepository.findById(user.actorId)!! + findById.delete(actor) + postRepository.save(findById) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPost.kt similarity index 85% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPost.kt index ef1c06c6..90ef8560 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPost.kt @@ -14,8 +14,8 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.common +package dev.usbharu.hideout.core.application.post -enum class ExtendedVocabulary { - Emoji -} +data class GetPost( + val postId: Long, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostApplicationService.kt new file mode 100644 index 00000000..e1d480f6 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostApplicationService.kt @@ -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.post + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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 org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetPostApplicationService(private val postRepository: PostRepository, transaction: Transaction) : + AbstractApplicationService(transaction, logger) { + + override suspend fun internalExecute(command: GetPost, executor: CommandExecutor): Post { + val post = postRepository.findById(PostId(command.postId)) ?: throw Exception("Post not found") + + return Post.of(post) + } + + companion object { + private val logger = LoggerFactory.getLogger(GetPostApplicationService::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/Post.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/Post.kt new file mode 100644 index 00000000..2ed11666 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/Post.kt @@ -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.post + +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 Post( + val id: Long, + val actorId: Long, + val overview: String?, + val text: String, + val content: String, + val createdAt: Instant, + val visibility: Visibility, + val url: URI, + val repostId: Long?, + val replyId: Long?, + val sensitive: Boolean, + val mediaIds: List, + val moveTo: Long?, +) { + companion object { + fun of(post: Post): dev.usbharu.hideout.core.application.post.Post { + return Post( + id = post.id.id, + actorId = post.actorId.id, + overview = post.overview?.overview, + text = post.text, + content = post.content.content, + createdAt = post.createdAt, + visibility = post.visibility, + url = post.url, + repostId = post.repostId?.id, + replyId = post.replyId?.id, + sensitive = post.sensitive, + mediaIds = post.mediaIds.map { it.id }, + moveTo = post.moveTo?.id + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostCreateDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPost.kt similarity index 68% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostCreateDto.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPost.kt index f2450dd4..16f1092e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostCreateDto.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPost.kt @@ -14,16 +14,17 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.post +package dev.usbharu.hideout.core.application.post import dev.usbharu.hideout.core.domain.model.post.Visibility -data class PostCreateDto( - val text: String, - val overview: String? = null, - val visibility: Visibility = Visibility.PUBLIC, - val repostId: Long? = null, - val repolyId: Long? = null, - val userId: Long, - val mediaIds: List = emptyList() +data class RegisterLocalPost( + val userDetailId: Long, + val content: String, + val overview: String?, + val visibility: Visibility, + val repostId: Long?, + val replyId: Long?, + val sensitive: Boolean, + val mediaIds: List, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPostApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPostApplicationService.kt new file mode 100644 index 00000000..181951b6 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/RegisterLocalPostApplicationService.kt @@ -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.post + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +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.userdetails.UserDetailRepository +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, + private val userDetailRepository: UserDetailRepository, + transaction: Transaction, +) : AbstractApplicationService(transaction, Companion.logger) { + + companion object { + val logger: Logger = LoggerFactory.getLogger(RegisterLocalPostApplicationService::class.java) + } + + override suspend fun internalExecute(command: RegisterLocalPost, executor: CommandExecutor): Long { + val actorId = ( + userDetailRepository.findById(command.userDetailId) + ?: throw IllegalStateException("actor not found") + ).actorId + + val actor = actorRepository.findById(actorId)!! + + 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 + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNote.kt similarity index 80% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNote.kt index 19d2cc8f..a8dcfb1a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNote.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.mastodon.interfaces.api.status +package dev.usbharu.hideout.core.application.post -data class StatusQuery( +data class UpdateLocalNote( val postId: Long, - val replyId: Long?, - val repostId: Long?, + val overview: String?, + val content: String, + val sensitive: Boolean, val mediaIds: List, - val emojiIds: List ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNoteApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNoteApplicationService.kt new file mode 100644 index 00000000..7a885791 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/UpdateLocalNoteApplicationService.kt @@ -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.post + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.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, +) : AbstractApplicationService(transaction, logger) { + + override suspend fun internalExecute(command: UpdateLocalNote, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + val post = postRepository.findById(PostId(command.postId))!! + + 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) + } +} diff --git a/hideout-core/src/intTest/kotlin/util/SpringApplicationTestBase.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/AcceptFollowRequest.kt similarity index 81% rename from hideout-core/src/intTest/kotlin/util/SpringApplicationTestBase.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/AcceptFollowRequest.kt index 158f8c7a..6c0d9f20 100644 --- a/hideout-core/src/intTest/kotlin/util/SpringApplicationTestBase.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/AcceptFollowRequest.kt @@ -14,9 +14,6 @@ * limitations under the License. */ -package util +package dev.usbharu.hideout.core.application.relationship.acceptfollowrequest -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -abstract class SpringApplicationTestBase +data class AcceptFollowRequest(val sourceActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/UserAcceptFollowRequestApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/UserAcceptFollowRequestApplicationService.kt new file mode 100644 index 00000000..1d4a3569 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/acceptfollowrequest/UserAcceptFollowRequestApplicationService.kt @@ -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.relationship.acceptfollowrequest + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserAcceptFollowRequestApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: AcceptFollowRequest, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.sourceActorId) + + val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) + ?: throw Exception("Follow request not found") + + relationship.acceptFollowRequest() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/Block.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/Block.kt new file mode 100644 index 00000000..7a095b92 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/Block.kt @@ -0,0 +1,19 @@ +/* + * 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.block + +data class Block(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/UserBlockApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/UserBlockApplicationService.kt new file mode 100644 index 00000000..e6d7e416 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/block/UserBlockApplicationService.kt @@ -0,0 +1,68 @@ +/* + * 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.block + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +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 userDetailRepository: UserDetailRepository, + private val relationshipDomainService: RelationshipDomainService, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Block, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + 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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/FollowRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/FollowRequest.kt new file mode 100644 index 00000000..3f8de0a7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/FollowRequest.kt @@ -0,0 +1,19 @@ +/* + * 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.followrequest + +data class FollowRequest(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/UserFollowRequestApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/UserFollowRequestApplicationService.kt new file mode 100644 index 00000000..52204beb --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/followrequest/UserFollowRequestApplicationService.kt @@ -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.relationship.followrequest + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserFollowRequestApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : AbstractApplicationService( + transaction, + logger +) { + + override suspend fun internalExecute(command: FollowRequest, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.followRequest() + + relationshipRepository.save(relationship) + } + + companion object { + private val logger = LoggerFactory.getLogger(UserFollowRequestApplicationService::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationship.kt new file mode 100644 index 00000000..90df1b82 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationship.kt @@ -0,0 +1,19 @@ +/* + * 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.get + +data class GetRelationship(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationshipApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationshipApplicationService.kt new file mode 100644 index 00000000..809f15b2 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/GetRelationshipApplicationService.kt @@ -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.relationship.get + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetRelationshipApplicationService( + private val relationshipRepository: RelationshipRepository, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, + private val actorInstanceRelationshipRepository: ActorInstanceRelationshipRepository, + transaction: Transaction, +) : + AbstractApplicationService( + transaction, + logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(GetRelationshipApplicationService::class.java) + } + + override suspend fun internalExecute(command: GetRelationship, executor: CommandExecutor): Relationship { + require(executor is UserDetailGettableCommandExecutor) + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + val targetId = ActorId(command.targetActorId) + val target = actorRepository.findById(targetId)!! + 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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/Relationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/Relationship.kt new file mode 100644 index 00000000..4893d62a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/get/Relationship.kt @@ -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.relationship.get + +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.relationship.get.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.isBlocking(), + domainMuting = actorInstanceRelationship.isMuting(), + domainDoNotSendPrivate = actorInstanceRelationship.isDoNotSendPrivate() + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/Mute.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/Mute.kt new file mode 100644 index 00000000..79a56830 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/Mute.kt @@ -0,0 +1,19 @@ +/* + * 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.mute + +data class Mute(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/UserMuteApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/UserMuteApplicationService.kt new file mode 100644 index 00000000..c3cafb42 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/mute/UserMuteApplicationService.kt @@ -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.relationship.mute + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserMuteApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Mute, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.mute() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/RejectFollowRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/RejectFollowRequest.kt new file mode 100644 index 00000000..4662eff1 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/RejectFollowRequest.kt @@ -0,0 +1,19 @@ +/* + * 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.rejectfollowrequest + +data class RejectFollowRequest(val sourceActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/UserRejectFollowRequestApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/UserRejectFollowRequestApplicationService.kt new file mode 100644 index 00000000..dd597e39 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/rejectfollowrequest/UserRejectFollowRequestApplicationService.kt @@ -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.relationship.rejectfollowrequest + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserRejectFollowRequestApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: RejectFollowRequest, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.sourceActorId) + + val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) + ?: throw Exception("Follow request not found") + + relationship.rejectFollowRequest() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/RemoveFromFollowers.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/RemoveFromFollowers.kt new file mode 100644 index 00000000..f9642099 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/RemoveFromFollowers.kt @@ -0,0 +1,19 @@ +/* + * 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.removefromfollowers + +data class RemoveFromFollowers(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/UserRemoveFromFollowersApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/UserRemoveFromFollowersApplicationService.kt new file mode 100644 index 00000000..122e6a59 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/removefromfollowers/UserRemoveFromFollowersApplicationService.kt @@ -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.relationship.removefromfollowers + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserRemoveFromFollowersApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: RemoveFromFollowers, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default( + targetId, + actor.id + ) + + relationship.unfollow() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/Unblock.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/Unblock.kt new file mode 100644 index 00000000..7b85c603 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/Unblock.kt @@ -0,0 +1,19 @@ +/* + * 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.unblock + +data class Unblock(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/UserUnblockApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/UserUnblockApplicationService.kt new file mode 100644 index 00000000..9de1ea7a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unblock/UserUnblockApplicationService.kt @@ -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.relationship.unblock + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserUnblockApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Unblock, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.unblock() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/Unfollow.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/Unfollow.kt new file mode 100644 index 00000000..60190dab --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/Unfollow.kt @@ -0,0 +1,19 @@ +/* + * 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.unfollow + +data class Unfollow(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/UserUnfollowApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/UserUnfollowApplicationService.kt new file mode 100644 index 00000000..4228b293 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unfollow/UserUnfollowApplicationService.kt @@ -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.relationship.unfollow + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserUnfollowApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Unfollow, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.unfollow() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/Unmute.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/Unmute.kt new file mode 100644 index 00000000..1939ee25 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/Unmute.kt @@ -0,0 +1,19 @@ +/* + * 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.unmute + +data class Unmute(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/UserUnmuteApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/UserUnmuteApplicationService.kt new file mode 100644 index 00000000..a7e5ae21 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/unmute/UserUnmuteApplicationService.kt @@ -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.relationship.unmute + +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +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.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserUnmuteApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Unmute, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.unmute() + + relationshipRepository.save(relationship) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/AbstractApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/AbstractApplicationService.kt new file mode 100644 index 00000000..720585dd --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/AbstractApplicationService.kt @@ -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.shared + +import kotlinx.coroutines.CancellationException +import org.slf4j.Logger + +abstract class AbstractApplicationService( + protected val transaction: Transaction, + protected val logger: Logger, +) : ApplicationService { + override suspend fun execute(command: T, executor: CommandExecutor): R { + return try { + logger.debug("START {} by {}", command::class.simpleName, executor) + val response = transaction.transaction { + internalExecute(command, executor) + } + logger.info("SUCCESS ${command::class.simpleName} by ${executor.executor}") + response + } catch (e: CancellationException) { + logger.debug("Coroutine canceled", e) + throw e + } catch (e: Exception) { + logger.warn("Command execution error", e) + throw e + } + } + + protected abstract suspend fun internalExecute(command: T, executor: CommandExecutor): R +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/ApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/ApplicationService.kt new file mode 100644 index 00000000..ad729fad --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/ApplicationService.kt @@ -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.shared + +interface ApplicationService { + suspend fun execute(command: T, executor: CommandExecutor): R +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/CommandExecutor.kt similarity index 75% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/CommandExecutor.kt index 2b8921ca..460d76cf 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/CommandExecutor.kt @@ -14,10 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.activity.create +package dev.usbharu.hideout.core.application.shared -import dev.usbharu.hideout.core.domain.model.post.Post - -interface ApSendCreateService { - suspend fun createNote(post: Post) +interface CommandExecutor { + val executor: String +} + +interface UserDetailGettableCommandExecutor : CommandExecutor { + val userDetailId: Long } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/Transaction.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/Transaction.kt similarity index 94% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/Transaction.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/Transaction.kt index f08e257f..5dc4df1b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/external/Transaction.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/shared/Transaction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.external +package dev.usbharu.hideout.core.application.shared import org.springframework.stereotype.Service diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MediaConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt similarity index 76% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MediaConfig.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt index 43d7cbf9..5cb5941c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MediaConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt @@ -14,11 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.config +package dev.usbharu.hideout.core.config import org.springframework.boot.context.properties.ConfigurationProperties +import java.net.URL -@ConfigurationProperties("hideout.media") -data class MediaConfig( - val remoteMediaFileSizeLimit: Long = 3000000L +@ConfigurationProperties("hideout") +data class ApplicationConfig( + val url: URL, + val private: Boolean = true, + val keySize: Int = 2048, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/FFmpegVideoConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/FFmpegVideoConfig.kt new file mode 100644 index 00000000..65e5f9be --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/FFmpegVideoConfig.kt @@ -0,0 +1,17 @@ +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 = listOf("preset", "ultrafast"), + val maxBitrate: Int = 1300000, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HtmlSanitizeConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/HtmlSanitizeConfig.kt similarity index 96% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HtmlSanitizeConfig.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/HtmlSanitizeConfig.kt index 10d5b076..e55bc8fe 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/HtmlSanitizeConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/HtmlSanitizeConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.config +package dev.usbharu.hideout.core.config import org.owasp.html.HtmlPolicyBuilder import org.owasp.html.PolicyFactory diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ImageIOImageConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ImageIOImageConfig.kt new file mode 100644 index 00000000..03e8bfcc --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ImageIOImageConfig.kt @@ -0,0 +1,10 @@ +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" +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/LocalStorageConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/LocalStorageConfig.kt new file mode 100644 index 00000000..48962c11 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/LocalStorageConfig.kt @@ -0,0 +1,17 @@ +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? +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/S3StorageConfig.kt similarity index 56% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/S3StorageConfig.kt index 2356b3d4..1a8b5677 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/S3StorageConfig.kt @@ -1,22 +1,7 @@ -/* - * 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.application.config +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 @@ -24,6 +9,17 @@ 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 diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt new file mode 100644 index 00000000..f32b4d9d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt @@ -0,0 +1,197 @@ +/* + * 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.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 + +@Configuration +@EnableWebSecurity(debug = true) +class SecurityConfig { + @Bean + fun passwordEncoder(): PasswordEncoder { + return BCryptPasswordEncoder() + } + + @Bean + @Order(1) + fun oauth2Provider(http: HttpSecurity): SecurityFilterChain { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) + http { + exceptionHandling { + authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/login") + } + } + return http.build() + } + + @Bean + @Order(3) + fun httpSecurityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + authorizeHttpRequests { + authorize("/error", permitAll) + authorize("/login", 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(anyRequest, authenticated) + } + formLogin { + } + } + return http.build() + } + + @Bean + fun registeredClientRepository(jdbcOperations: JdbcOperations): RegisteredClientRepository { + return JdbcRegisteredClientRepository(jdbcOperations) + } + + @Bean + fun oauth2AuthorizationConsentService( + jdbcOperations: JdbcOperations, + registeredClientRepository: RegisteredClientRepository, + ): OAuth2AuthorizationConsentService { + return JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository) + } + + @Bean + fun authorizationServerSettings(): AuthorizationServerSettings { + return AuthorizationServerSettings.builder().authorizationEndpoint("/oauth/authorize") + .tokenEndpoint("/oauth/token").tokenRevocationEndpoint("/oauth/revoke").build() + } + + @Bean + fun jwtTokenCustomizer(): OAuth2TokenCustomizer { + return OAuth2TokenCustomizer { context: JwtEncodingContext -> + + if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType && + context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE + ) { + val userDetailsImpl = context.getPrincipal().principal as HideoutUserDetails + context.claims.claim("uid", userDetailsImpl.userDetailsId.toString()) + } + } + } + + @Bean + fun loadJwkSource(jwkConfig: JwkConfig): JWKSource { + 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( + val keyId: String, + val publicKey: String, + val 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 + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MvcConfigurer.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt similarity index 97% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MvcConfigurer.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt index 05c1898c..ee75a0ef 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/MvcConfigurer.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.config +package dev.usbharu.hideout.core.config import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor import org.springframework.context.annotation.Bean diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actor/ActorEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actor/ActorEvent.kt new file mode 100644 index 00000000..69e154eb --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actor/ActorEvent.kt @@ -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.domain.event.actor + +import dev.usbharu.hideout.core.domain.model.actor.Actor +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 { + return DomainEvent.create( + actorEvent.eventName, + ActorEventBody(actor), + actorEvent.collectable + ) + } +} + +class ActorEventBody(actor: Actor) : 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"), +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actorinstancerelationship/ActorInstanceRelationshipEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actorinstancerelationship/ActorInstanceRelationshipEvent.kt new file mode 100644 index 00000000..5ff3fc5f --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actorinstancerelationship/ActorInstanceRelationshipEvent.kt @@ -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.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 { + return DomainEvent.create( + actorInstanceRelationshipEvent.eventName, + ActorInstanceRelationshipEventBody(actorInstanceRelationship) + ) + } +} + +class ActorInstanceRelationshipEventBody(actorInstanceRelationship: ActorInstanceRelationship) : + DomainEventBody( + mapOf( + "actorId" to actorInstanceRelationship.actorId, + "instanceId" to actorInstanceRelationship.instanceId, + "muting" to actorInstanceRelationship.isMuting(), + "blocking" to actorInstanceRelationship.isBlocking(), + "doNotSendPrivate" to actorInstanceRelationship.isDoNotSendPrivate(), + ) + ) + +enum class ActorInstanceRelationshipEvent(val eventName: String) { + BLOCK("ActorInstanceBlock"), + MUTE("ActorInstanceMute"), + UNMUTE("ActorInstanceUnmute"), +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/instance/InstanceEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/instance/InstanceEvent.kt new file mode 100644 index 00000000..72f0941a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/instance/InstanceEvent.kt @@ -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 { + return DomainEvent.create( + event.eventName, + InstanceEventBody(instance) + ) + } +} + +class InstanceEventBody(instance: Instance) : DomainEventBody(mapOf("instance" to instance)) + +enum class InstanceEvent(val eventName: String) { + UPDATE("InstanceUpdate") +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/post/PostEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/post/PostEvent.kt new file mode 100644 index 00000000..5020d056 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/post/PostEvent.kt @@ -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.domain.event.post + +import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.post.Post +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 { + return DomainEvent.create( + postEvent.eventName, + PostEventBody(post, actor) + ) + } +} + +class PostEventBody(post: Post, actor: Actor?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) + +enum class PostEvent(val eventName: String) { + DELETE("PostDelete"), + UPDATE("PostUpdate"), + CREATE("PostCreate"), + CHECK_UPDATE("PostCheckUpdate"), +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt new file mode 100644 index 00000000..89adf3e8 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt @@ -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.domain.event.relationship + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody + +class RelationshipEventFactory(private val relationship: Relationship) { + fun createEvent(relationshipEvent: RelationshipEvent): DomainEvent { + return DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship)) + } +} + +class RelationshipEventBody(relationship: Relationship) : DomainEventBody(mapOf("relationship" to relationship)) + +enum class RelationshipEvent(val eventName: String) { + FOLLOW("RelationshipFollow"), + UNFOLLOW("RelationshipUnfollow"), + BLOCK("RelationshipBlock"), + UNBLOCK("RelationshipUnblock"), + MUTE("RelationshipMute"), + UNMUTE("RelationshipUnmute"), + FOLLOW_REQUEST("RelationshipFollowRequest"), + UNFOLLOW_REQUEST("RelationshipUnfollowRequest"), +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/UserNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/UserNotFoundException.kt deleted file mode 100644 index a70c9256..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/UserNotFoundException.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 - -class UserNotFoundException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 6343548635914580823L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaConvertException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaConvertException.kt deleted file mode 100644 index 08e693c9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaConvertException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -open class MediaConvertException : MediaException { - 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 = -6349105549968160551L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt deleted file mode 100644 index b406ae99..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaException.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -@Suppress("UnnecessaryAbstractClass") -abstract class MediaException : 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 = 5988922562494187852L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeException.kt deleted file mode 100644 index 7a99d7c3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -open class MediaFileSizeException : MediaException { - 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 = 8672626879026555064L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeIsZeroException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeIsZeroException.kt deleted file mode 100644 index 12b0dba0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaFileSizeIsZeroException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -class MediaFileSizeIsZeroException : MediaFileSizeException { - 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 = -2398394583775317875L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaProcessException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaProcessException.kt deleted file mode 100644 index fc4bff6c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaProcessException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -class MediaProcessException : MediaException { - 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 = -5195233013542703735L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaSaveException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaSaveException.kt deleted file mode 100644 index d4fddbf6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/MediaSaveException.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.media - -open class MediaSaveException : MediaException { - 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 - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/RemoteMediaFileSizeException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/RemoteMediaFileSizeException.kt deleted file mode 100644 index ed52372b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/RemoteMediaFileSizeException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -class RemoteMediaFileSizeException : MediaFileSizeException { - 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 = 9188247721397839435L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/UnsupportedMediaException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/UnsupportedMediaException.kt deleted file mode 100644 index 1a97958f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/media/UnsupportedMediaException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.media - -import java.io.Serial - -class UnsupportedMediaException : MediaException { - 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 = -116741513216017134L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/PostNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/PostNotFoundException.kt deleted file mode 100644 index b723fa1a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/PostNotFoundException.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 PostNotFoundException : NotFoundException { - 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 = 1315818410686905717L - - fun withApId(apId: String): PostNotFoundException = PostNotFoundException("apId: $apId was not found.") - - fun withId(id: Long): PostNotFoundException = PostNotFoundException("id: $id was not found.") - - fun withUrl(url: String): PostNotFoundException = PostNotFoundException("url: $url was not found.") - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/UserNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/UserNotFoundException.kt deleted file mode 100644 index 223e7820..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/UserNotFoundException.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 - -open class UserNotFoundException : NotFoundException { - 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 = 3219433672235626200L - - fun withName(string: String, throwable: Throwable? = null): UserNotFoundException = - UserNotFoundException("name: $string was not found.", throwable) - - fun withId(id: Long, throwable: Throwable? = null): UserNotFoundException = - UserNotFoundException("id: $id was not found.", throwable) - - fun withUrl(url: String, throwable: Throwable? = null): UserNotFoundException = - UserNotFoundException("url: $url was not found.", throwable) - - fun withNameAndDomain(name: String, domain: String, throwable: Throwable? = null): UserNotFoundException = - UserNotFoundException("name: $name domain: $domain (@$name@$domain) was not found.", throwable) - - fun withKeyId(keyId: String, throwable: Throwable? = null) = - UserNotFoundException("keyId: $keyId was not found.", throwable) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/local/LocalUserNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/local/LocalUserNotFoundException.kt deleted file mode 100644 index 939b711c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/resource/local/LocalUserNotFoundException.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.local - -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import java.io.Serial - -class LocalUserNotFoundException : UserNotFoundException { - 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 = -4742548128672528145L - - fun withName(string: String, throwable: Throwable? = null): LocalUserNotFoundException = - LocalUserNotFoundException("name: $string was not found.", throwable) - - fun withId(id: Long, throwable: Throwable? = null): LocalUserNotFoundException = - LocalUserNotFoundException("id: $id was not found.", throwable) - - fun withUrl(url: String, throwable: Throwable? = null): LocalUserNotFoundException = - LocalUserNotFoundException("url: $url was not found.", throwable) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index eee1e824..45d34c4d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -16,253 +16,133 @@ package dev.usbharu.hideout.core.domain.model.actor -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.CharacterLimit -import jakarta.validation.Validator -import jakarta.validation.constraints.* -import org.hibernate.validator.constraints.URL -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component +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.EmojiId +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.shared.Domain +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable +import java.net.URI import java.time.Instant -import kotlin.math.max -data class Actor private constructor( - @get:NotNull - @get:Positive - val id: Long, - @get:Pattern(regexp = "^[a-zA-Z0-9_-]{1,300}\$") - @get:Size(min = 1) - val name: String, - val domain: String, - val screenName: String, - val description: String, - @get:URL - val inbox: String, - @get:URL - val outbox: String, - @get:URL - val url: String, - @get:NotBlank - val publicKey: String, - val privateKey: String? = null, - @get:PastOrPresent +@Suppress("LongParameterList") +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, - @get:NotBlank - val keyId: String, - val followers: String? = null, - val following: String? = null, - @get:PositiveOrZero - val instance: Long, - val locked: Boolean, - val followersCount: Int = 0, - val followingCount: Int = 0, - val postsCount: Int = 0, - val lastPostDate: Instant? = null, - val emojis: List = emptyList(), -) { + 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 = emptySet(), + moveTo: ActorId? = null, + emojiIds: Set, + deleted: Boolean, + roles: Set, + icon: MediaId?, + banner: MediaId?, +) : DomainEventStorable() { - @Component - class UserBuilder( - private val characterLimit: CharacterLimit, - private val applicationConfig: ApplicationConfig, - private val validator: Validator, - ) { + var banner = banner + private set - private val logger = LoggerFactory.getLogger(UserBuilder::class.java) + fun setBannerUrl(banner: MediaId?, actor: Actor) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + this.banner = banner + } - @Suppress("LongParameterList", "FunctionMinLength", "LongMethod") - fun of( - id: Long, - name: String, - domain: String, - screenName: String, - description: String, - inbox: String, - outbox: String, - url: String, - publicKey: String, - privateKey: String? = null, - createdAt: Instant, - keyId: String, - following: String? = null, - followers: String? = null, - instance: Long, - locked: Boolean, - followersCount: Int = 0, - followingCount: Int = 0, - postsCount: Int = 0, - lastPostDate: Instant? = null, - emojis: List = emptyList(), - ): Actor { - if (id == 0L) { - return Actor( - id = id, - name = name, - domain = domain, - screenName = screenName, - description = description, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = privateKey, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - instance = instance, - locked = locked, - followersCount = followersCount, - followingCount = followingCount, - postsCount = postsCount, - lastPostDate = lastPostDate, - emojis = emojis - ) + var icon = icon + private set + + fun setIconUrl(icon: MediaId?, actor: Actor) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + this.icon = icon + } + + var roles = roles + private set + + fun setRole(roles: Set, actor: Actor) { + require(actor.roles.contains(Role.ADMINISTRATOR).not()) + + this.roles = roles + } + + 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 + } - // idは0未満ではいけない - require(id >= 0) { "id must be greater than or equal to 0." } + var alsoKnownAs = alsoKnownAs + set(value) { + require(value.find { it == id } == null) + field = value + } - // nameは空文字以外を含める必要がある - require(name.isNotBlank()) { "name must contain non-blank characters." } + var moveTo = moveTo + set(value) { + require(value != id) + addDomainEvent(ActorDomainEventFactory(this).createEvent(MOVE)) + field = value + } - // nameは指定された長さ以下である必要がある - val limitedName = if (name.length >= characterLimit.account.id) { - logger.warn("name must not exceed ${characterLimit.account.id} characters.") - name.substring(0, characterLimit.account.id) - } else { - name - } + var emojis = emojiIds + private set - // domainは空文字以外を含める必要がある - require(domain.isNotBlank()) { "domain must contain non-blank characters." } + var description = description + set(value) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + field = value + } + var screenName = screenName + set(value) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + field = value + } - // domainは指定された長さ以下である必要がある - require(domain.length <= characterLimit.general.domain) { - "domain must not exceed ${characterLimit.general.domain} characters." - } + var deleted = deleted + private set - // screenNameは空文字以外を含める必要がある - require(screenName.isNotBlank()) { "screenName must contain non-blank characters." } - - // screenNameは指定された長さ以下である必要がある - val limitedScreenName = if (screenName.length >= characterLimit.account.name) { - logger.warn("screenName must not exceed ${characterLimit.account.name} characters.") - screenName.substring(0, characterLimit.account.name) - } else { - screenName - } - - // descriptionは指定された長さ以下である必要がある - val limitedDescription = if (description.length >= characterLimit.account.description) { - logger.warn("description must not exceed ${characterLimit.account.description} characters.") - description.substring(0, characterLimit.account.description) - } else { - description - } - - // ローカルユーザーはpasswordとprivateKeyをnullにしてはいけない - if (domain == applicationConfig.url.host) { - requireNotNull(privateKey) { "password and privateKey must not be null for local users." } - } - - // urlは空文字以外を含める必要がある - require(url.isNotBlank()) { "url must contain non-blank characters." } - - // urlは指定された長さ以下である必要がある - require(url.length <= characterLimit.general.url) { - "url must not exceed ${characterLimit.general.url} characters." - } - - // inboxは空文字以外を含める必要がある - require(inbox.isNotBlank()) { "inbox must contain non-blank characters." } - - // inboxは指定された長さ以下である必要がある - require(inbox.length <= characterLimit.general.url) { - "inbox must not exceed ${characterLimit.general.url} characters." - } - - // outboxは空文字以外を含める必要がある - require(outbox.isNotBlank()) { "outbox must contain non-blank characters." } - - // outboxは指定された長さ以下である必要がある - require(outbox.length <= characterLimit.general.url) { - "outbox must not exceed ${characterLimit.general.url} characters." - } - - require(keyId.isNotBlank()) { - "keyId must contain non-blank characters." - } - - val actor = Actor( - id = id, - name = limitedName, - domain = domain, - screenName = limitedScreenName, - description = limitedDescription, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = privateKey, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - instance = instance, - locked = locked, - followersCount = max(0, followersCount), - followingCount = max(0, followingCount), - postsCount = max(0, postsCount), - lastPostDate = lastPostDate, - emojis = emojis - ) - - val validate = validator.validate(actor) - - for (constraintViolation in validate) { - throw IllegalArgumentException("${constraintViolation.propertyPath} : ${constraintViolation.message}") - } - return actor + 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 incrementFollowing(): Actor = this.copy(followingCount = this.followingCount + 1) + fun restore() { + deleted = false + checkUpdate() + } - fun decrementFollowing(): Actor = this.copy(followingCount = this.followingCount - 1) - - fun incrementFollowers(): Actor = this.copy(followersCount = this.followersCount + 1) - - fun decrementFollowers(): Actor = this.copy(followersCount = this.followersCount - 1) - - fun incrementPostsCount(): Actor = this.copy(postsCount = this.postsCount + 1) - - fun decrementPostsCount(): Actor = this.copy(postsCount = this.postsCount - 1) - - fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate) - override fun toString(): String { - return "Actor(" + - "id=$id, " + - "name='$name', " + - "domain='$domain', " + - "screenName='$screenName', " + - "description='$description', " + - "inbox='$inbox', " + - "outbox='$outbox', " + - "url='$url', " + - "publicKey='$publicKey', " + - "privateKey=$privateKey, " + - "createdAt=$createdAt, " + - "keyId='$keyId', " + - "followers=$followers, " + - "following=$following, " + - "instance=$instance, " + - "locked=$locked, " + - "followersCount=$followersCount, " + - "followingCount=$followingCount, " + - "postsCount=$postsCount, " + - "lastPostDate=$lastPostDate, " + - "emojis=$emojis" + - ")" + fun checkUpdate() { + addDomainEvent(ActorDomainEventFactory(this).createEvent(CHECK_UPDATE)) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt new file mode 100644 index 00000000..740e34d5 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt @@ -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.domain.model.actor + +class ActorDescription(description: String) { + val description: String = description.take(length) + + companion object { + val length = 10000 + val empty = ActorDescription("") + } +} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImplTest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorId.kt similarity index 75% rename from hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImplTest.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorId.kt index affa4b36..19e295f4 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImplTest.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorId.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media - - -import org.junit.jupiter.api.Test - -class MediaServiceImplTest { - @Test - fun png画像をアップロードできる() { +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) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorKeyId.kt similarity index 85% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorKeyId.kt index c8861b42..ccf98962 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorKeyId.kt @@ -16,4 +16,5 @@ package dev.usbharu.hideout.core.domain.model.actor -data class Acct(val username: String, val domain: String? = null, val isRemote: Boolean = domain == null) +@JvmInline +value class ActorKeyId(val keyId: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/HttpSignatureVerifyException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorName.kt similarity index 65% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/HttpSignatureVerifyException.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorName.kt index ef22cec4..6819fc8e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/HttpSignatureVerifyException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorName.kt @@ -14,17 +14,18 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.exception +package dev.usbharu.hideout.core.domain.model.actor -import java.io.Serial -import javax.naming.AuthenticationException - -class HttpSignatureVerifyException : AuthenticationException { - constructor() : super() - constructor(s: String?) : super(s) +@JvmInline +value class ActorName(val name: String) { + init { + require(name.isNotBlank()) + require(name.length <= length) + require(regex.matches(name)) + } companion object { - @Serial - private const val serialVersionUID: Long = 1484943321770741944L + val length = 300 + private val regex = Regex("^[a-zA-Z0-9_-]{1,$length}\$") } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPostsCount.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPostsCount.kt new file mode 100644 index 00000000..8c344c05 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPostsCount.kt @@ -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 ActorPostsCount(val postsCount: Int) { + init { + require(0 <= this.postsCount) { "Posts count must be greater than 0" } + } + + companion object { + val ZERO = ActorPostsCount(0) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPrivateKey.kt similarity index 50% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPrivateKey.kt index ae84181d..bb507885 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPrivateKey.kt @@ -14,18 +14,24 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.exception +package dev.usbharu.hideout.core.domain.model.actor -import java.io.Serial - -class IllegalActivityPubObjectException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) +import java.security.PrivateKey +import java.util.* +@JvmInline +value class ActorPrivateKey(val privateKey: String) { companion object { - @Serial - private const val serialVersionUID: Long = 7216998115771415263L + fun create(privateKey: PrivateKey): ActorPrivateKey { + return ActorPrivateKey( + "-----BEGIN PRIVATE KEY-----\n" + + Base64 + .getEncoder() + .encodeToString(privateKey.encoded) + .chunked(64) + .joinToString("\n") + + "\n-----END PRIVATE KEY-----" + ) + } } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/FailedToGetResourcesException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKey.kt similarity index 50% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/FailedToGetResourcesException.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKey.kt index cda900d4..5dd982f1 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/FailedToGetResourcesException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKey.kt @@ -14,18 +14,24 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.exception +package dev.usbharu.hideout.core.domain.model.actor -import java.io.Serial - -open class FailedToGetResourcesException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) +import java.security.PublicKey +import java.util.* +@JvmInline +value class ActorPublicKey(val publicKey: String) { companion object { - @Serial - private const val serialVersionUID: Long = -3117221954866309059L + fun create(publicKey: PublicKey): ActorPublicKey { + return ActorPublicKey( + "-----BEGIN PUBLIC KEY-----\n" + + Base64 + .getEncoder() + .encodeToString(publicKey.encoded) + .chunked(64) + .joinToString("\n") + + "\n-----END PUBLIC KEY-----" + ) + } } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRelationshipCount.kt similarity index 73% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRelationshipCount.kt index 80d4828b..e21b75f4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRelationshipCount.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.activity.undo +package dev.usbharu.hideout.core.domain.model.actor -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APSendUndoService { - suspend fun sendUndoFollow(actor: Actor, target: Actor) +@JvmInline +value class ActorRelationshipCount(val relationshipCount: Int) { + init { + require(0 <= relationshipCount) { "Followers count must be > 0" } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt index 0123961c..266e1d47 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt @@ -16,34 +16,9 @@ package dev.usbharu.hideout.core.domain.model.actor -import org.springframework.stereotype.Repository - -@Repository -@Suppress("TooManyFunctions") interface ActorRepository { suspend fun save(actor: Actor): Actor - - suspend fun findById(id: Long): Actor? - - suspend fun findByIdWithLock(id: Long): Actor? - - suspend fun findAll(limit: Int, offset: Long): List - - suspend fun findByName(name: String): List - + suspend fun delete(actor: Actor) + suspend fun findById(id: ActorId): Actor? suspend fun findByNameAndDomain(name: String, domain: String): Actor? - - suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor? - - suspend fun findByUrl(url: String): Actor? - - suspend fun findByUrlWithLock(url: String): Actor? - - suspend fun findByIds(ids: List): List - - suspend fun findByKeyId(keyId: String): Actor? - - suspend fun delete(id: Long) - - suspend fun nextId(): Long } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/MetaRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt similarity index 71% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/MetaRepository.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt index 31807823..30b4aff3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/MetaRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.meta +package dev.usbharu.hideout.core.domain.model.actor -import org.springframework.stereotype.Repository +class ActorScreenName(screenName: String) { -@Repository -interface MetaRepository { + val screenName: String = screenName.take(length) - suspend fun save(meta: Meta) - - suspend fun get(): Meta? + companion object { + val length = 300 + val empty = ActorScreenName("") + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Role.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Role.kt new file mode 100644 index 00000000..39c3acd4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Role.kt @@ -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.domain.model.actor + +enum class Role { + LOCAL, MODERATOR, ADMINISTRATOR, REMOTE +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt new file mode 100644 index 00000000..f4244ca5 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt @@ -0,0 +1,110 @@ +/* + * 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.actorinstancerelationship + +import dev.usbharu.hideout.core.domain.event.actorinstancerelationship.ActorInstanceRelationshipDomainEventFactory +import dev.usbharu.hideout.core.domain.event.actorinstancerelationship.ActorInstanceRelationshipEvent.* +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable + +data class ActorInstanceRelationship( + val actorId: ActorId, + val instanceId: InstanceId, + private var blocking: Boolean = false, + private var muting: Boolean = false, + private var doNotSendPrivate: Boolean = false, +) : DomainEventStorable() { + fun block(): ActorInstanceRelationship { + addDomainEvent(ActorInstanceRelationshipDomainEventFactory(this).createEvent(BLOCK)) + blocking = true + return this + } + + fun unblock(): ActorInstanceRelationship { + blocking = false + return this + } + + fun mute(): ActorInstanceRelationship { + addDomainEvent(ActorInstanceRelationshipDomainEventFactory(this).createEvent(MUTE)) + muting = true + return this + } + + fun unmute(): ActorInstanceRelationship { + addDomainEvent(ActorInstanceRelationshipDomainEventFactory(this).createEvent(UNMUTE)) + muting = false + return this + } + + fun doNotSendPrivate(): ActorInstanceRelationship { + doNotSendPrivate = true + return this + } + + fun doSendPrivate(): ActorInstanceRelationship { + doNotSendPrivate = false + return this + } + + fun isBlocking() = blocking + + fun isMuting() = muting + + fun isDoNotSendPrivate() = doNotSendPrivate + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ActorInstanceRelationship + + if (actorId != other.actorId) return false + if (instanceId != other.instanceId) return false + + return true + } + + override fun hashCode(): Int { + var result = actorId.hashCode() + result = 31 * result + instanceId.hashCode() + return result + } + + override fun toString(): String { + return "ActorInstanceRelationship(" + + "actorId=$actorId, " + + "instanceId=$instanceId, " + + "blocking=$blocking, " + + "muting=$muting, " + + "doNotSendPrivate=$doNotSendPrivate" + + ")" + } + + companion object { + fun default(actorId: ActorId, instanceId: InstanceId): ActorInstanceRelationship { + return ActorInstanceRelationship( + actorId = actorId, + instanceId = instanceId, + blocking = false, + muting = false, + doNotSendPrivate = false + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt similarity index 52% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt index 841641cc..ea2bba54 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt @@ -14,18 +14,13 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.exception +package dev.usbharu.hideout.core.domain.model.actorinstancerelationship -import java.io.Serial +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId -class JsonParseException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 7975567796830950692L - } +interface ActorInstanceRelationshipRepository { + suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship + suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship) + suspend fun findByActorIdAndInstanceId(actorId: ActorId, instanceId: InstanceId): ActorInstanceRelationship? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMedia.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/Application.kt similarity index 80% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMedia.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/Application.kt index 8b1a1aff..968bb90e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMedia.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/Application.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.domain.model.application -data class ProcessedMedia( - val file: ProcessedFile, - val thumbnail: ProcessedFile? +class Application( + val applicationId: ApplicationId, + val name: ApplicationName, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationId.kt new file mode 100644 index 00000000..6daadcf0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationId.kt @@ -0,0 +1,20 @@ +/* + * 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.application + +@JvmInline +value class ApplicationId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationName.kt new file mode 100644 index 00000000..6b9f9a77 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationName.kt @@ -0,0 +1,20 @@ +/* + * 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.application + +@JvmInline +value class ApplicationName(val name: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationRepository.kt new file mode 100644 index 00000000..0150e266 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/application/ApplicationRepository.kt @@ -0,0 +1,22 @@ +/* + * 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.application + +interface ApplicationRepository { + suspend fun save(application: Application): Application + suspend fun delete(application: Application) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt deleted file mode 100644 index 42c3d5b0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.deletedActor - -interface DeletedActorRepository { - suspend fun save(deletedActor: DeletedActor): DeletedActor - suspend fun delete(deletedActor: DeletedActor) - suspend fun findById(id: Long): DeletedActor? - suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt index b50b29df..34240b2e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmoji.kt @@ -16,10 +16,13 @@ package dev.usbharu.hideout.core.domain.model.emoji +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.shared.Domain +import java.net.URI import java.time.Instant sealed class Emoji { - abstract val domain: String + abstract val domain: Domain abstract val name: String @Suppress("FunctionMinLength") @@ -33,13 +36,13 @@ sealed class Emoji { } data class CustomEmoji( - val id: Long, + val id: EmojiId, override val name: String, - override val domain: String, - val instanceId: Long?, - val url: String, + override val domain: Domain, + val instanceId: InstanceId, + val url: URI, val category: String?, - val createdAt: Instant + val createdAt: Instant, ) : Emoji() { override fun id(): String = id.toString() } @@ -47,6 +50,6 @@ data class CustomEmoji( data class UnicodeEmoji( override val name: String ) : Emoji() { - override val domain: String = "unicode.org" + override val domain: Domain = Domain("unicode.org") override fun id(): String = name } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt index eaac7bd5..1eb20f2c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/CustomEmojiRepository.kt @@ -17,9 +17,9 @@ package dev.usbharu.hideout.core.domain.model.emoji interface CustomEmojiRepository { - suspend fun generateId(): Long suspend fun save(customEmoji: CustomEmoji): CustomEmoji suspend fun findById(id: Long): CustomEmoji? suspend fun delete(customEmoji: CustomEmoji) - suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji? + suspend fun findByNamesAndDomain(names: List, domain: String): List + suspend fun findByIds(ids: List): List } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt new file mode 100644 index 00000000..0ba25e1a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt @@ -0,0 +1,20 @@ +/* + * 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.emoji + +@JvmInline +value class EmojiId(val emojiId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt index 10bffd91..41a835b3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt @@ -1,25 +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.domain.model.filter -data class Filter( - val id: Long, - val userId: Long, - val name: String, - val context: List, +import dev.usbharu.hideout.core.domain.model.filter.Filter.Companion.Action.SET_KEYWORDS +import dev.usbharu.hideout.core.domain.model.filter.FilterMode.* +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId + +class Filter( + val id: FilterId, + val userDetailId: UserDetailId, + var name: FilterName, + val filterContext: Set, val filterAction: FilterAction, -) + filterKeywords: Set, +) { + var filterKeywords = filterKeywords + private set + + fun setFilterKeywords(filterKeywords: Set, user: UserDetail) { + require(isAllow(user, SET_KEYWORDS, this)) + this.filterKeywords = filterKeywords + } + + fun compileFilter(): Regex { + val words = mutableListOf() + val wholeWords = mutableListOf() + val regexes = mutableListOf() + + for (filterKeyword in filterKeywords) { + when (filterKeyword.mode) { + WHOLE_WORD -> wholeWords.add(filterKeyword.keyword.keyword) + REGEX -> regexes.add(filterKeyword.keyword.keyword) + NONE -> words.add(filterKeyword.keyword.keyword) + } + } + + return (wholeWords + regexes + wholeWords) + .joinToString("|") + .toRegex() + } + + fun reconstructWith(filterKeywords: Set): Filter { + return Filter( + id = this.id, + userDetailId = this.userDetailId, + name = this.name, + filterContext = this.filterContext, + filterAction = this.filterAction, + filterKeywords = filterKeywords + ) + } + + companion object { + fun isAllow(user: UserDetail, action: Action, resource: Filter): Boolean { + return when (action) { + SET_KEYWORDS -> resource.userDetailId == user.id + } + } + + enum class Action { + SET_KEYWORDS + } + + @Suppress("LongParameterList") + fun create( + id: FilterId, + userDetailId: UserDetailId, + name: FilterName, + filterContext: Set, + filterAction: FilterAction, + filterKeywords: Set, + ): Filter { + return Filter( + id, + userDetailId, + name, + filterContext, + filterAction, + filterKeywords + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterAction.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterAction.kt index 6eca79d8..d19c8db8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterAction.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterAction.kt @@ -1,23 +1,6 @@ -/* - * 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.filter -@Suppress("EnumEntryNameCase", "EnumNaming") enum class FilterAction { - warn, - hide + WARN, + HIDE } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterContext.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterContext.kt new file mode 100644 index 00000000..df987e3d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterContext.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.core.domain.model.filter + +enum class FilterContext { + HOME, + NOTIFICATION, + PUBLIC, + THREAD, + ACCOUNT +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterId.kt new file mode 100644 index 00000000..ce42a4c4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterId.kt @@ -0,0 +1,4 @@ +package dev.usbharu.hideout.core.domain.model.filter + +@JvmInline +value class FilterId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt new file mode 100644 index 00000000..0e8774ba --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt @@ -0,0 +1,17 @@ +package dev.usbharu.hideout.core.domain.model.filter + +import dev.usbharu.hideout.core.domain.model.filter.FilterMode.* + +class FilterKeyword( + val id: FilterKeywordId, + var keyword: FilterKeywordKeyword, + val mode: FilterMode +) { + fun match(string: String): Boolean { + when (mode) { + WHOLE_WORD -> TODO() + REGEX -> TODO() + NONE -> TODO() + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordId.kt new file mode 100644 index 00000000..4f1b2df5 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordId.kt @@ -0,0 +1,4 @@ +package dev.usbharu.hideout.core.domain.model.filter + +@JvmInline +value class FilterKeywordId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordKeyword.kt new file mode 100644 index 00000000..89e959b3 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeywordKeyword.kt @@ -0,0 +1,4 @@ +package dev.usbharu.hideout.core.domain.model.filter + +@JvmInline +value class FilterKeywordKeyword(val keyword: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterMode.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterMode.kt index 10a20ec5..57e38fb7 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterMode.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterMode.kt @@ -1,19 +1,3 @@ -/* - * 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.filter enum class FilterMode { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt new file mode 100644 index 00000000..09e424de --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt @@ -0,0 +1,4 @@ +package dev.usbharu.hideout.core.domain.model.filter + +@JvmInline +value class FilterName(val name: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt index ef0ba01b..88ff3feb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterRepository.kt @@ -1,30 +1,9 @@ -/* - * 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.filter interface FilterRepository { - - suspend fun generateId(): Long suspend fun save(filter: Filter): Filter - suspend fun findById(id: Long): Filter? + suspend fun delete(filter: Filter) - suspend fun findByUserIdAndId(userId: Long, id: Long): Filter? - suspend fun findByUserIdAndType(userId: Long, types: List): List - suspend fun deleteById(id: Long) - - suspend fun deleteByUserIdAndId(userId: Long, id: Long) + suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? + suspend fun findByFilterId(filterId: FilterId): Filter? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt new file mode 100644 index 00000000..478f05e5 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.core.domain.model.filter + +class FilterResult(val filter: Filter, val matchedKeyword: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterType.kt deleted file mode 100644 index be815999..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterType.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.filter - -@Suppress("EnumEntryNameCase", "EnumNaming") -enum class FilterType { - home, - notifications, - public, - thread, - account -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt new file mode 100644 index 00000000..80226401 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.core.domain.model.filter + +import dev.usbharu.hideout.core.domain.model.post.Post + +class FilteredPost(val post: Post, val filterResults: List) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeywordRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeywordRepository.kt deleted file mode 100644 index eae3bdde..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filterkeyword/FilterKeywordRepository.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.filterkeyword - -interface FilterKeywordRepository { - suspend fun generateId(): Long - suspend fun save(filterKeyword: FilterKeyword): FilterKeyword - suspend fun saveAll(filterKeywordList: List) - suspend fun findById(id: Long): FilterKeyword? - suspend fun deleteById(id: Long) - suspend fun deleteByFilterId(filterId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Instance.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Instance.kt index 3d0aac30..4bda1989 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Instance.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Instance.kt @@ -16,19 +16,44 @@ package dev.usbharu.hideout.core.domain.model.instance +import dev.usbharu.hideout.core.domain.event.instance.InstanceEvent +import dev.usbharu.hideout.core.domain.event.instance.InstanceEventFactory +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable +import java.net.URI import java.time.Instant -data class Instance( - val id: Long, - val name: String, - val description: String, - val url: String, - val iconUrl: String, - val sharedInbox: String?, - val software: String, - val version: String, - val isBlocked: Boolean, - val isMuted: Boolean, - val moderationNote: String, - val createdAt: Instant -) +@Suppress("LongParameterList") +class Instance( + val id: InstanceId, + var name: InstanceName, + var description: InstanceDescription, + val url: URI, + iconUrl: URI, + var sharedInbox: URI?, + var software: InstanceSoftware, + var version: InstanceVersion, + var isBlocked: Boolean, + var isMuted: Boolean, + var moderationNote: InstanceModerationNote, + val createdAt: Instant, +) : DomainEventStorable() { + + var iconUrl = iconUrl + set(value) { + addDomainEvent(InstanceEventFactory(this).createEvent(InstanceEvent.UPDATE)) + field = value + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Instance + + return id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceDescription.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceDescription.kt new file mode 100644 index 00000000..8a6f2084 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceDescription.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceDescription(val description: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceId.kt new file mode 100644 index 00000000..66ed056b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceId.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceId(val instanceId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceModerationNote.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceModerationNote.kt new file mode 100644 index 00000000..6439f75c --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceModerationNote.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceModerationNote(val note: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceName.kt new file mode 100644 index 00000000..5133566e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceName.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceName(val name: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceRepository.kt index 121e5adc..5446d3bd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceRepository.kt @@ -16,10 +16,11 @@ package dev.usbharu.hideout.core.domain.model.instance +import java.net.URI + interface InstanceRepository { - suspend fun generateId(): Long suspend fun save(instance: Instance): Instance - suspend fun findById(id: Long): Instance? + suspend fun findById(id: InstanceId): Instance? suspend fun delete(instance: Instance) - suspend fun findByUrl(url: String): Instance? + suspend fun findByUrl(url: URI): Instance? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceSoftware.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceSoftware.kt new file mode 100644 index 00000000..30d06746 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceSoftware.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceSoftware(val software: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceVersion.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceVersion.kt new file mode 100644 index 00000000..b8770133 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/InstanceVersion.kt @@ -0,0 +1,20 @@ +/* + * 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.instance + +@JvmInline +value class InstanceVersion(val version: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt deleted file mode 100644 index d1c60cd7..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.instance - -class Nodeinfo private constructor() { - - var links: List = emptyList() - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Nodeinfo - - return links == other.links - } - - override fun hashCode(): Int = links.hashCode() -} - -class Links private constructor() { - var rel: String? = null - var href: String? = null - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Links - - if (rel != other.rel) return false - if (href != other.href) return false - - return true - } - - override fun hashCode(): Int { - var result = rel?.hashCode() ?: 0 - result = 31 * result + (href?.hashCode() ?: 0) - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt deleted file mode 100644 index efa0da33..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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. - */ - -@file:Suppress("Filename") - -package dev.usbharu.hideout.core.domain.model.instance - -@Suppress("ClassNaming") -class Nodeinfo2_0 { - var metadata: Metadata? = null - var software: Software? = null - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Nodeinfo2_0 - - if (metadata != other.metadata) return false - if (software != other.software) return false - - return true - } - - override fun hashCode(): Int { - var result = metadata?.hashCode() ?: 0 - result = 31 * result + (software?.hashCode() ?: 0) - return result - } -} - -class Metadata { - var nodeName: String? = null - var nodeDescription: String? = null - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Metadata - - if (nodeName != other.nodeName) return false - if (nodeDescription != other.nodeDescription) return false - - return true - } - - override fun hashCode(): Int { - var result = nodeName?.hashCode() ?: 0 - result = 31 * result + (nodeDescription?.hashCode() ?: 0) - return result - } -} - -class Software { - var name: String? = null - var version: String? = null - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Software - - if (name != other.name) return false - if (version != other.version) return false - - return true - } - - override fun hashCode(): Int { - var result = name?.hashCode() ?: 0 - result = 31 * result + (version?.hashCode() ?: 0) - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/FileType.kt similarity index 92% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileType.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/FileType.kt index 111b85a7..9d2b8837 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileType.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/FileType.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.domain.model.media enum class FileType { Image, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/Media.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/Media.kt index fe9f2883..1d2823c4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/Media.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/Media.kt @@ -16,34 +16,36 @@ package dev.usbharu.hideout.core.domain.model.media -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MimeType -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import java.net.URI -data class Media( - val id: Long, - val name: String, - val url: String, - val remoteUrl: String?, - val thumbnailUrl: String?, +class Media( + val id: MediaId, + val name: MediaName, + url: URI, + val remoteUrl: URI?, + val thumbnailUrl: URI?, val type: FileType, val mimeType: MimeType, - val blurHash: String?, - val description: String? = null -) + val blurHash: MediaBlurHash?, + val description: MediaDescription? = null, +) { + var url = url + private set -fun Media.toMediaAttachments(): MediaAttachment = MediaAttachment( - id = id.toString(), - type = when (type) { - FileType.Image -> MediaAttachment.Type.image - FileType.Video -> MediaAttachment.Type.video - FileType.Audio -> MediaAttachment.Type.audio - FileType.Unknown -> MediaAttachment.Type.unknown - }, - url = url, - previewUrl = thumbnailUrl, - remoteUrl = remoteUrl, - description = description, - blurhash = blurHash, - textUrl = url -) + fun setUrl(url: URI) { + this.url = url + } + override fun toString(): String { + return "Media(" + + "id=$id, " + + "name=$name, " + + "url=$url, " + + "remoteUrl=$remoteUrl, " + + "thumbnailUrl=$thumbnailUrl, " + + "type=$type, " + + "mimeType=$mimeType, " + + "blurHash=$blurHash, " + + "description=$description" + + ")" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaBlurHash.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaBlurHash.kt new file mode 100644 index 00000000..1b1dd054 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaBlurHash.kt @@ -0,0 +1,20 @@ +/* + * 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.media + +@JvmInline +value class MediaBlurHash(val hash: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaDescription.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaDescription.kt new file mode 100644 index 00000000..c99345b0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaDescription.kt @@ -0,0 +1,20 @@ +/* + * 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.media + +@JvmInline +value class MediaDescription(val description: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaId.kt new file mode 100644 index 00000000..5003f164 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaId.kt @@ -0,0 +1,20 @@ +/* + * 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.media + +@JvmInline +value class MediaId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaName.kt new file mode 100644 index 00000000..58c483ac --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaName.kt @@ -0,0 +1,20 @@ +/* + * 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.media + +@JvmInline +value class MediaName(val name: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt index a3bd8e37..cc5321b8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt @@ -17,9 +17,7 @@ package dev.usbharu.hideout.core.domain.model.media interface MediaRepository { - suspend fun generateId(): Long suspend fun save(media: Media): Media - suspend fun findById(id: Long): Media? - suspend fun delete(id: Long) - suspend fun findByRemoteUrl(remoteUrl: String): Media? + suspend fun findById(id: MediaId): Media? + suspend fun delete(media: Media) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MimeType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MimeType.kt similarity index 92% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MimeType.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MimeType.kt index 0194f3b5..95cdcd99 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MimeType.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MimeType.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.domain.model.media data class MimeType(val type: String, val subtype: String, val fileType: FileType) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Jwt.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Jwt.kt deleted file mode 100644 index 0893efe5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Jwt.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.meta - -import java.util.* - -data class Jwt(val kid: UUID, val privateKey: String, val publicKey: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Meta.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Meta.kt deleted file mode 100644 index e26594ae..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/meta/Meta.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.meta - -data class Meta(val version: String, val jwt: Jwt) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt deleted file mode 100644 index 56e8f33d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.notification - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ExposedNotificationRepository(private val idGenerateService: IdGenerateService) : NotificationRepository, - AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(notification: Notification): Notification = query { - val singleOrNull = - Notifications.selectAll().where { Notifications.id eq notification.id }.forUpdate().singleOrNull() - if (singleOrNull == null) { - Notifications.insert { - it[id] = notification.id - it[type] = notification.type - it[userId] = notification.userId - it[sourceActorId] = notification.sourceActorId - it[postId] = notification.postId - it[text] = notification.text - it[reactionId] = notification.reactionId - it[createdAt] = notification.createdAt - } - } else { - Notifications.update({ Notifications.id eq notification.id }) { - it[type] = notification.type - it[userId] = notification.userId - it[sourceActorId] = notification.sourceActorId - it[postId] = notification.postId - it[text] = notification.text - it[reactionId] = notification.reactionId - it[createdAt] = notification.createdAt - } - } - notification - } - - override suspend fun findById(id: Long): Notification? = query { - Notifications.selectAll().where { Notifications.id eq id }.singleOrNull()?.toNotifications() - } - - override suspend fun deleteById(id: Long) { - Notifications.deleteWhere { Notifications.id eq id } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedNotificationRepository::class.java) - } -} - -fun ResultRow.toNotifications() = Notification( - id = this[Notifications.id], - type = this[Notifications.type], - userId = this[Notifications.userId], - sourceActorId = this[Notifications.sourceActorId], - postId = this[Notifications.postId], - text = this[Notifications.text], - reactionId = this[Notifications.reactionId], - createdAt = this[Notifications.createdAt], -) - -object Notifications : Table("notifications") { - val id = long("id") - val type = varchar("type", 100) - val userId = long("user_id").references(Actors.id) - val sourceActorId = long("source_actor_id").references(Actors.id).nullable() - val postId = long("post_id").references(Posts.id).nullable() - val text = varchar("text", 3000).nullable() - val reactionId = long("reaction_id").references(Reactions.id).nullable() - val createdAt = timestamp("created_at") - - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt deleted file mode 100644 index 24d557db..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/NotificationRepository.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.notification - -interface NotificationRepository { - suspend fun generateId(): Long - suspend fun save(notification: Notification): Notification - suspend fun findById(id: Long): Notification? - suspend fun deleteById(id: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt index 2cd1d39b..9bf9211c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt @@ -16,204 +16,308 @@ package dev.usbharu.hideout.core.domain.model.post -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.core.service.post.PostContentFormatter -import jakarta.validation.Validator -import jakarta.validation.constraints.Positive -import org.hibernate.validator.constraints.URL -import org.springframework.stereotype.Component +import dev.usbharu.hideout.core.domain.event.post.PostDomainEventFactory +import dev.usbharu.hideout.core.domain.event.post.PostEvent +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.actor.Role +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId +import dev.usbharu.hideout.core.domain.model.media.MediaId +import dev.usbharu.hideout.core.domain.model.post.Post.Companion.Action.* +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable +import java.net.URI import java.time.Instant -data class Post private constructor( - @get:Positive - val id: Long, - @get:Positive - val actorId: Long, - val overview: String? = null, - val content: String, - val text: String, - @get:Positive - val createdAt: Long, - val visibility: Visibility, - @get:URL - val url: String, - val repostId: Long? = null, - val replyId: Long? = null, - val sensitive: Boolean = false, - @get:URL - val apId: String = url, - val mediaIds: List = emptyList(), - val deleted: Boolean = false, - val emojiIds: List = emptyList(), -) { +@Suppress("LongParameterList", "TooManyFunctions") +class Post( + val id: PostId, + actorId: ActorId, + overview: PostOverview?, + content: PostContent, + val createdAt: Instant, + visibility: Visibility, + val url: URI, + val repostId: PostId?, + val replyId: PostId?, + sensitive: Boolean, + val apId: URI, + deleted: Boolean, + mediaIds: List, + visibleActors: Set, + hide: Boolean, + moveTo: PostId?, +) : DomainEventStorable() { - @Component - class PostBuilder( - private val characterLimit: CharacterLimit, - private val postContentFormatter: PostContentFormatter, - private val validator: Validator, - ) { - @Suppress("FunctionMinLength", "LongParameterList") - fun of( - id: Long, - actorId: Long, - overview: String? = null, - content: String, - createdAt: Long, + val actorId = actorId + get() { + if (deleted) { + return ActorId.ghost + } + return field + } + + var visibility = visibility + private set + + fun setVisibility(visibility: Visibility, actor: Actor) { + require(isAllow(actor, UPDATE, this)) + require(this.visibility != Visibility.DIRECT) + require(visibility != Visibility.DIRECT) + require(this.visibility.ordinal >= visibility.ordinal) + + require(deleted.not()) + + if (this.visibility != visibility) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + } + this.visibility = visibility + } + + var visibleActors = visibleActors + private set + + fun setVisibleActors(visibleActors: Set, actor: Actor) { + require(isAllow(actor, UPDATE, this)) + require(deleted.not()) + if (visibility == Visibility.DIRECT) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + this.visibleActors = this.visibleActors.plus(visibleActors) + } + } + + var content = content + get() { + if (hide) { + return PostContent.empty + } + return field + } + private set + + fun setContent(content: PostContent, actor: Actor) { + require(isAllow(actor, UPDATE, this)) + require(deleted.not()) + if (this.content != content) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + } + this.content = content + } + + var overview = overview + get() { + if (hide) { + return null + } + return field + } + private set + + fun setOverview(overview: PostOverview?, actor: Actor) { + require(isAllow(actor, UPDATE, this)) + require(deleted.not()) + if (this.overview != overview) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + } + this.overview = overview + } + + var sensitive = sensitive + private set + + fun setSensitive(sensitive: Boolean, actor: Actor) { + isAllow(actor, UPDATE, this) + require(deleted.not()) + if (this.sensitive != sensitive) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + } + this.sensitive = sensitive + } + + val text: String + get() { + if (hide) { + return PostContent.empty.text + } + return content.text + } + + val emojiIds: List + get() { + if (hide) { + return PostContent.empty.emojiIds + } + return content.emojiIds + } + + var mediaIds = mediaIds + get() { + if (hide) { + return emptyList() + } + return field + } + private set + + fun addMediaIds(mediaIds: List, actor: Actor) { + require(isAllow(actor, UPDATE, this)) + require(deleted.not()) + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.UPDATE)) + this.mediaIds = this.mediaIds.plus(mediaIds).distinct() + } + + var deleted = deleted + private set + + fun delete(actor: Actor) { + isAllow(actor, DELETE, this) + if (deleted.not()) { + addDomainEvent(PostDomainEventFactory(this, actor).createEvent(PostEvent.DELETE)) + content = PostContent.empty + overview = null + mediaIds = emptyList() + } + deleted = true + } + + fun checkUpdate() { + addDomainEvent(PostDomainEventFactory(this).createEvent(PostEvent.CHECK_UPDATE)) + } + + fun restore(content: PostContent, overview: PostOverview?, mediaIds: List) { + deleted = false + this.content = content + this.overview = overview + this.mediaIds = mediaIds + checkUpdate() + } + + var hide = hide + private set + + fun hide() { + hide = true + } + + fun show() { + hide = false + } + + var moveTo = moveTo + private set + + fun moveTo(moveTo: PostId, actor: Actor) { + require(isAllow(actor, MOVE, this)) + require(this.moveTo == null) + this.moveTo = moveTo + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Post + + return id == other.id + } + + override fun hashCode(): Int = id.hashCode() + + fun reconstructWith(mediaIds: List, emojis: List, visibleActors: Set): Post { + return Post( + id = id, + actorId = actorId, + overview = overview, + content = PostContent(this.content.text, this.content.content, emojis), + createdAt = createdAt, + visibility = visibility, + url = url, + repostId = repostId, + replyId = replyId, + sensitive = sensitive, + apId = apId, + deleted = deleted, + mediaIds = mediaIds, + visibleActors = visibleActors, + hide = hide, + moveTo = moveTo + ) + } + + companion object { + @Suppress("LongParameterList") + fun create( + id: PostId, + actorId: ActorId, + overview: PostOverview? = null, + content: PostContent, + createdAt: Instant, visibility: Visibility, - url: String, - repostId: Long? = null, - replyId: Long? = null, - sensitive: Boolean = false, - apId: String = url, - mediaIds: List = emptyList(), - emojiIds: List = emptyList(), - deleted: Boolean = false, + url: URI, + repostId: PostId?, + replyId: PostId?, + sensitive: Boolean, + apId: URI, + deleted: Boolean, + mediaIds: List, + visibleActors: Set = emptySet(), + hide: Boolean = false, + moveTo: PostId? = null, + actor: Actor, ): Post { - require(id >= 0) { "id must be greater than or equal to 0." } + require(actor.deleted.not()) + require(actor.moveTo == null) - require(actorId >= 0) { "actorId must be greater than or equal to 0." } - - val limitedOverview = if ((overview?.length ?: 0) >= characterLimit.post.overview) { - overview?.substring(0, characterLimit.post.overview) + val visibility1 = if (actor.suspend && visibility == Visibility.PUBLIC) { + Visibility.UNLISTED } else { - overview + visibility } - val limitedText = if (content.length >= characterLimit.post.text) { - content.substring(0, characterLimit.post.text) - } else { - content - } - - val (html, content1) = postContentFormatter.format(limitedText) - - require(url.isNotBlank()) { "url must contain non-blank characters" } - require(url.length <= characterLimit.general.url) { - "url must not exceed ${characterLimit.general.url} characters." - } - - require((repostId ?: 0) >= 0) { "repostId must be greater then or equal to 0." } - require((replyId ?: 0) >= 0) { "replyId must be greater then or equal to 0." } - val post = Post( id = id, actorId = actorId, - overview = limitedOverview, - content = html, - text = content1, + overview = overview, + content = content, createdAt = createdAt, - visibility = visibility, + visibility = visibility1, url = url, repostId = repostId, replyId = replyId, sensitive = sensitive, apId = apId, - mediaIds = mediaIds, deleted = deleted, - emojiIds = emojiIds - ) - - val validate = validator.validate(post) - - for (constraintViolation in validate) { - throw IllegalArgumentException("${constraintViolation.propertyPath} : ${constraintViolation.message}") - } - - if (post.deleted) { - return post.delete() - } - - return post - } - - @Suppress("LongParameterList") - fun pureRepostOf( - id: Long, - actorId: Long, - visibility: Visibility, - createdAt: Instant, - url: String, - repost: Post, - apId: String, - ): Post { - // リポストの公開範囲は元のポストより広くてはいけない - val fixedVisibility = if (visibility.ordinal <= repost.visibility.ordinal) { - repost.visibility - } else { - visibility - } - - val post = of( - id = id, - actorId = actorId, - overview = null, - content = "", - createdAt = createdAt.toEpochMilli(), - visibility = fixedVisibility, - url = url, - repostId = repost.id, - replyId = null, - sensitive = false, - apId = apId, - mediaIds = emptyList(), - deleted = false, - emojiIds = emptyList() - ) - return post - } - - @Suppress("LongParameterList") - fun quoteRepostOf( - id: Long, - actorId: Long, - overview: String? = null, - content: String, - createdAt: Instant, - visibility: Visibility, - url: String, - repost: Post, - replyId: Long? = null, - sensitive: Boolean = false, - apId: String = url, - mediaIds: List = emptyList(), - emojiIds: List = emptyList(), - ): Post { - // リポストの公開範囲は元のポストより広くてはいけない - val fixedVisibility = if (visibility.ordinal <= repost.visibility.ordinal) { - repost.visibility - } else { - visibility - } - - val post = of( - id = id, - actorId = actorId, - overview = overview, - content = content, - createdAt = createdAt.toEpochMilli(), - visibility = fixedVisibility, - url = url, - repostId = repost.id, - replyId = replyId, - sensitive = sensitive, - apId = apId, mediaIds = mediaIds, - deleted = false, - emojiIds = emojiIds + visibleActors = visibleActors, + hide = hide, + moveTo = moveTo ) + post.addDomainEvent(PostDomainEventFactory(post).createEvent(PostEvent.CREATE)) return post } + + fun isAllow(actor: Actor, action: Action, resource: Post): Boolean { + return when (action) { + UPDATE -> { + if (actor.deleted) { + return true + } + resource.actorId == actor.id || actor.roles.contains(Role.ADMINISTRATOR) || actor.roles.contains( + Role.MODERATOR + ) + } + + MOVE -> resource.actorId == actor.id && actor.deleted.not() + DELETE -> + resource.actorId == actor.id || + actor.roles.contains(Role.ADMINISTRATOR) || + actor.roles.contains(Role.MODERATOR) + } + } + + enum class Action { + UPDATE, + MOVE, + DELETE, + } } - - fun isPureRepost(): Boolean = - this.text.isEmpty() && - this.content.isEmpty() && - this.overview == null && - this.replyId == null && - this.repostId != null - - fun delete(): Post = copy(deleted = true) - - fun restore(): Post = copy(deleted = false) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt similarity index 62% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt index 03249388..2d787e75 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt @@ -14,16 +14,15 @@ * limitations under the License. */ -package dev.usbharu.hideout.util +package dev.usbharu.hideout.core.domain.model.post -class CollectionUtil +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId -fun Iterable.singleOr(block: (e: RuntimeException) -> Throwable): T { - return try { - this.single() - } catch (e: NoSuchElementException) { - throw block(e) - } catch (e: IllegalArgumentException) { - throw block(e) +data class PostContent(val text: String, val content: String, val emojiIds: List) { + + companion object { + val empty = PostContent("", "", emptyList()) + const val CONTENT_LENGTH = 5000 + const val TEXT_LENGTH = 3000 } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostId.kt new file mode 100644 index 00000000..e4682ff1 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostId.kt @@ -0,0 +1,20 @@ +/* + * 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.post + +@JvmInline +value class PostId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt new file mode 100644 index 00000000..0793febe --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt @@ -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.domain.model.post + +@JvmInline +value class PostOverview(val overview: String) { + companion object { + const val LENGTH = 100 + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt index 561911f9..ddd781a7 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt @@ -16,22 +16,12 @@ package dev.usbharu.hideout.core.domain.model.post -import org.springframework.stereotype.Repository +import dev.usbharu.hideout.core.domain.model.actor.ActorId -@Suppress("LongParameterList", "TooManyFunctions") -@Repository interface PostRepository { - suspend fun generateId(): Long suspend fun save(post: Post): Post - suspend fun saveAll(posts: List) - suspend fun delete(id: Long) - suspend fun findById(id: Long): Post? - suspend fun findByUrl(url: String): Post? - - suspend fun findByApId(apId: String): Post? - suspend fun existByApIdWithLock(apId: String): Boolean - suspend fun findByActorId(actorId: Long): List - suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List - - suspend fun countByActorId(actorId: Long): Int + suspend fun saveAll(posts: List): List + suspend fun findById(id: PostId): Post? + suspend fun findByActorId(id: ActorId): List + suspend fun delete(post: Post) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt deleted file mode 100644 index e497788b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/Reaction.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.reaction - -import dev.usbharu.hideout.core.domain.model.emoji.Emoji - -data class Reaction(val id: Long, val emoji: Emoji, val postId: Long, val actorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt deleted file mode 100644 index 493fcdac..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.reaction - -import dev.usbharu.hideout.core.domain.model.emoji.Emoji -import org.springframework.stereotype.Repository - -@Repository -@Suppress("FunctionMaxLength", "TooManyFunctions") -interface ReactionRepository { - suspend fun generateId(): Long - suspend fun save(reaction: Reaction): Reaction - suspend fun delete(reaction: Reaction): Reaction - suspend fun deleteByPostId(postId: Long): Int - suspend fun deleteByActorId(actorId: Long): Int - suspend fun deleteByPostIdAndActorId(postId: Long, actorId: Long) - suspend fun deleteByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji) - suspend fun findById(id: Long): Reaction? - suspend fun findByPostId(postId: Long): List - suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? - suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean - suspend fun existByPostIdAndActorIdAndUnicodeEmoji(postId: Long, actorId: Long, unicodeEmoji: String): Boolean - suspend fun existByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Boolean - suspend fun existByPostIdAndActor(postId: Long, actorId: Long): Boolean - suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt index cbdaa3ea..068de901 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt @@ -16,23 +16,120 @@ package dev.usbharu.hideout.core.domain.model.relationship -/** - * ユーザーとの関係を表します - * - * @property actorId ユーザー - * @property targetActorId 相手ユーザー - * @property following フォローしているか - * @property blocking ブロックしているか - * @property muting ミュートしているか - * @property followRequest フォローリクエストを送っているか - * @property ignoreFollowRequestToTarget フォローリクエストを無視しているか - */ -data class Relationship( - val actorId: Long, - val targetActorId: Long, - val following: Boolean, - val blocking: Boolean, - val muting: Boolean, - val followRequest: Boolean, - val ignoreFollowRequestToTarget: Boolean -) +import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent +import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventFactory +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable + +class Relationship( + val actorId: ActorId, + val targetActorId: ActorId, + following: Boolean, + blocking: Boolean, + muting: Boolean, + followRequesting: Boolean, + mutingFollowRequest: Boolean, +) : DomainEventStorable() { + + var following: Boolean = following + private set + var blocking: Boolean = blocking + private set + + var muting: Boolean = muting + private set + var followRequesting: Boolean = followRequesting + private set + var mutingFollowRequest: Boolean = mutingFollowRequest + private set + + fun follow() { + require(blocking.not()) + following = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW)) + } + + fun unfollow() { + following = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNFOLLOW)) + } + + fun block() { + require(following.not()) + blocking = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.BLOCK)) + } + + fun unblock() { + blocking = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNBLOCK)) + } + + fun mute() { + muting = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.MUTE)) + } + + fun unmute() { + muting = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNMUTE)) + } + + fun muteFollowRequest() { + mutingFollowRequest = true + } + + fun unmuteFollowRequest() { + mutingFollowRequest = false + } + + fun followRequest() { + require(blocking.not()) + followRequesting = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW_REQUEST)) + } + + fun unfollowRequest() { + followRequesting = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNFOLLOW_REQUEST)) + } + + fun acceptFollowRequest() { + follow() + followRequesting = false + } + + fun rejectFollowRequest() { + followRequesting = false + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Relationship + + if (actorId != other.actorId) return false + if (targetActorId != other.targetActorId) return false + + return true + } + + override fun hashCode(): Int { + var result = actorId.hashCode() + result = 31 * result + targetActorId.hashCode() + return result + } + + companion object { + fun default(actorId: ActorId, targetActorId: ActorId): Relationship = Relationship( + actorId = actorId, + targetActorId = targetActorId, + following = false, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index 4780d30d..d74884af 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -16,57 +16,10 @@ package dev.usbharu.hideout.core.domain.model.relationship -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList +import dev.usbharu.hideout.core.domain.model.actor.ActorId -/** - * [Relationship]の永続化 - * - */ interface RelationshipRepository { - /** - * 永続化します - * - * @param relationship 永続化する[Relationship] - * @return 永続化された[Relationship] - */ suspend fun save(relationship: Relationship): Relationship - - /** - * 永続化されたものを削除します - * - * @param relationship 削除する[Relationship] - */ suspend fun delete(relationship: Relationship) - - /** - * userIdとtargetUserIdで[Relationship]を取得します - * - * @param actorId 取得するユーザーID - * @param targetActorId 対象ユーザーID - * @return 取得された[Relationship] 存在しない場合nullが返ります - */ - suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? - - suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) - - suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List - - suspend fun countByTargetIdAndFollowing(targetId: Long, following: Boolean): Int - - suspend fun countByUserIdAndFollowing(userId: Long, following: Boolean): Int - - @Suppress("FunctionMaxLength") - suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( - targetId: Long, - followRequest: Boolean, - ignoreFollowRequest: Boolean, - page: Page.PageByMaxId, - ): PaginationList - - suspend fun findByActorIdAndMuting( - actorId: Long, - muting: Boolean, - page: Page.PageByMaxId, - ): PaginationList + suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt deleted file mode 100644 index 481c3dd6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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.relationship - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination -import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(relationship: Relationship): Relationship = query { - val singleOrNull = Relationships.selectAll().where { - (Relationships.actorId eq relationship.actorId).and( - Relationships.targetActorId eq relationship.targetActorId - ) - }.forUpdate().singleOrNull() - - if (singleOrNull == null) { - Relationships.insert { - it[actorId] = relationship.actorId - it[targetActorId] = relationship.targetActorId - it[following] = relationship.following - it[blocking] = relationship.blocking - it[muting] = relationship.muting - it[followRequest] = relationship.followRequest - it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget - } - } else { - Relationships.update({ - (Relationships.actorId eq relationship.actorId).and( - Relationships.targetActorId eq relationship.targetActorId - ) - }) { - it[following] = relationship.following - it[blocking] = relationship.blocking - it[muting] = relationship.muting - it[followRequest] = relationship.followRequest - it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget - } - } - return@query relationship - } - - override suspend fun delete(relationship: Relationship): Unit = query { - Relationships.deleteWhere { - (actorId eq relationship.actorId).and( - targetActorId eq relationship.targetActorId - ) - } - } - - override suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? = query { - return@query Relationships.selectAll() - .where { (Relationships.actorId eq actorId).and(Relationships.targetActorId eq targetActorId) } - .singleOrNull()?.toRelationships() - } - - override suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long): Unit = query { - Relationships.deleteWhere { - Relationships.actorId.eq(actorId).or(Relationships.targetActorId.eq(targetActorId)) - } - } - - override suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List = query { - return@query Relationships - .selectAll().where { Relationships.targetActorId eq targetId and (Relationships.following eq following) } - .map { it.toRelationships() } - } - - override suspend fun countByTargetIdAndFollowing(targetId: Long, following: Boolean): Int = query { - return@query Relationships - .selectAll() - .where { - Relationships.targetActorId eq targetId and (Relationships.following eq following) - } - .count() - .toInt() - } - - override suspend fun countByUserIdAndFollowing(userId: Long, following: Boolean): Int = query { - return@query Relationships - .selectAll() - .where { - Relationships.actorId eq userId and (Relationships.following eq following) - } - .count() - .toInt() - } - - override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( - targetId: Long, - followRequest: Boolean, - ignoreFollowRequest: Boolean, - page: Page.PageByMaxId, - ): PaginationList = query { - val query = Relationships.selectAll().where { - Relationships.targetActorId.eq(targetId).and(Relationships.followRequest.eq(followRequest)) - .and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest)) - } - - val resultRowList = query.withPagination(page, Relationships.id) - - return@query PaginationList( - resultRowList.map { it.toRelationships() }, - resultRowList.next?.value, - resultRowList.prev?.value - ) - } - - override suspend fun findByActorIdAndMuting( - actorId: Long, - muting: Boolean, - page: Page.PageByMaxId, - ): PaginationList = query { - val query = - Relationships.selectAll().where { Relationships.actorId.eq(actorId).and(Relationships.muting.eq(muting)) } - - val resultRowList = query.withPagination(page, Relationships.id) - - return@query PaginationList( - resultRowList.map { it.toRelationships() }, - resultRowList.next?.value, - resultRowList.prev?.value - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(RelationshipRepositoryImpl::class.java) - } -} - -fun ResultRow.toRelationships(): Relationship = Relationship( - actorId = this[Relationships.actorId], - targetActorId = this[Relationships.targetActorId], - following = this[Relationships.following], - blocking = this[Relationships.blocking], - muting = this[Relationships.muting], - followRequest = this[Relationships.followRequest], - ignoreFollowRequestToTarget = this[Relationships.ignoreFollowRequestFromTarget] -) - -object Relationships : LongIdTable("relationships") { - val actorId = long("actor_id").references(Actors.id) - val targetActorId = long("target_actor_id").references(Actors.id) - val following = bool("following") - val blocking = bool("blocking") - val muting = bool("muting") - val followRequest = bool("follow_request") - val ignoreFollowRequestFromTarget = bool("ignore_follow_request") - - init { - uniqueIndex(actorId, targetActorId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/Domain.kt similarity index 72% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/Domain.kt index 97b1355d..cf6f9f1b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/Domain.kt @@ -14,10 +14,15 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.objects.note +package dev.usbharu.hideout.core.domain.model.shared -import dev.usbharu.hideout.activitypub.domain.model.Note +@JvmInline +value class Domain(val domain: String) { + init { + require(domain.length <= LENGTH) + } -interface NoteApApiService { - suspend fun getNote(postId: Long, userId: Long?): Note? + companion object { + const val LENGTH = 1000 + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt deleted file mode 100644 index 46e4548a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.timeline - -import dev.usbharu.hideout.core.domain.model.post.Visibility -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.index.CompoundIndex -import org.springframework.data.mongodb.core.mapping.Document - -@Document -@CompoundIndex(def = "{'userId':1,'timelineId':1,'postId':1}", unique = true) -data class Timeline( - @Id - val id: Long, - val userId: Long, - val timelineId: Long, - val postId: Long, - val postActorId: Long, - val createdAt: Long, - val replyId: Long?, - val repostId: Long?, - val visibility: Visibility, - val sensitive: Boolean, - val isLocal: Boolean, - val isPureRepost: Boolean = false, - val mediaIds: List = emptyList(), - val emojiIds: List = emptyList() -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/TimelineRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/TimelineRepository.kt deleted file mode 100644 index 21c8d2ee..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/TimelineRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.timeline - -interface TimelineRepository { - suspend fun generateId(): Long - suspend fun save(timeline: Timeline): Timeline - suspend fun saveAll(timelines: List): List - suspend fun findByUserId(id: Long): List - suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt index aabaa0ce..e41a86b8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt @@ -16,8 +16,43 @@ package dev.usbharu.hideout.core.domain.model.userdetails -data class UserDetail( - val actorId: Long, - val password: String, - val autoAcceptFolloweeFollowRequest: Boolean -) +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import java.time.Instant + +class UserDetail private constructor( + val id: UserDetailId, + val actorId: ActorId, + var password: UserDetailHashedPassword, + var autoAcceptFolloweeFollowRequest: Boolean, + var lastMigration: Instant? = null, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UserDetail + + return id == other.id + } + + override fun hashCode(): Int = id.hashCode() + + companion object { + fun create( + id: UserDetailId, + actorId: ActorId, + password: UserDetailHashedPassword, + autoAcceptFolloweeFollowRequest: Boolean = false, + lastMigration: Instant? = null, + ): UserDetail { + return UserDetail( + id, + actorId, + password, + autoAcceptFolloweeFollowRequest, + lastMigration + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt new file mode 100644 index 00000000..f0dc4399 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt @@ -0,0 +1,20 @@ +/* + * 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.userdetails + +@JvmInline +value class UserDetailHashedPassword(val password: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt new file mode 100644 index 00000000..cc048546 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt @@ -0,0 +1,20 @@ +/* + * 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.userdetails + +@JvmInline +value class UserDetailId(val id: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailRepository.kt index 54f2e84c..08e562bd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailRepository.kt @@ -20,4 +20,5 @@ interface UserDetailRepository { suspend fun save(userDetail: UserDetail): UserDetail suspend fun delete(userDetail: UserDetail) suspend fun findByActorId(actorId: Long): UserDetail? + suspend fun findById(id: Long): UserDetail? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt similarity index 57% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt index 64b2f8c2..a2f9b6fd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt @@ -14,12 +14,17 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.query +package dev.usbharu.hideout.core.domain.service.actor +import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.Actor +import org.springframework.stereotype.Service -@Deprecated("Use RelationshipQueryService") -interface FollowerQueryService { - suspend fun findFollowersById(id: Long): List - suspend fun alreadyFollow(actorId: Long, followerId: Long): Boolean +interface IRemoteActorCheckDomainService { + fun isRemoteActor(actor: Actor): Boolean +} + +@Service +class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService { + override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain != applicationConfig.url.host } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainService.kt similarity index 62% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainService.kt index 8d76227b..1dafb03d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainService.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.query +package dev.usbharu.hideout.core.domain.service.actor.local -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.core.domain.model.post.Post +import dev.usbharu.hideout.core.domain.model.actor.ActorPrivateKey +import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey -interface NoteQueryService { - suspend fun findById(id: Long): Pair? - suspend fun findByApid(apId: String): Pair? +interface LocalActorDomainService { + suspend fun usernameAlreadyUse(name: String): Boolean + suspend fun generateKeyPair(): Pair } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt new file mode 100644 index 00000000..6f0dd6de --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt @@ -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.domain.service.actor.local + +import dev.usbharu.hideout.core.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.model.actor.ActorPrivateKey +import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import org.springframework.stereotype.Service +import java.security.KeyPairGenerator + +@Service +class LocalActorDomainServiceImpl( + private val actorRepository: ActorRepository, + private val applicationConfig: ApplicationConfig, +) : LocalActorDomainService { + override suspend fun usernameAlreadyUse(name: String): Boolean = + actorRepository.findByNameAndDomain(name, applicationConfig.url.host) != null + + override suspend fun generateKeyPair(): Pair { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(applicationConfig.keySize) + val generateKeyPair = keyPairGenerator.generateKeyPair() + + return ActorPublicKey.create(generateKeyPair.public) to ActorPrivateKey.create(generateKeyPair.private) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainService.kt similarity index 51% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainService.kt index f1d42b42..3c1adf00 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainService.kt @@ -14,27 +14,24 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.user +package dev.usbharu.hideout.core.domain.service.actor.local import dev.usbharu.hideout.core.domain.model.actor.Actor -import org.springframework.stereotype.Service -@Service -interface UserService { - - suspend fun usernameAlreadyUse(username: String): Boolean - - suspend fun createLocalUser(user: UserCreateDto): Actor - - suspend fun createRemoteUser(user: RemoteUserCreateDto, idOverride: Long? = null): Actor - - suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) - - suspend fun deleteRemoteActor(actorId: Long) - - suspend fun restorationRemoteActor(actorId: Long) - - suspend fun deleteLocalUser(userId: Long) - - suspend fun updateUserStatistics(userId: Long) +interface LocalActorMigrationCheckDomainService { + suspend fun canAccountMigration(from: Actor, to: Actor): AccountMigrationCheck +} + +sealed class AccountMigrationCheck( + val canMigration: Boolean, +) { + class CanAccountMigration : AccountMigrationCheck(true) + + class CircularReferences(val message: String) : AccountMigrationCheck(false) + + class SelfReferences : AccountMigrationCheck(false) + + class AlreadyMoved(val message: String) : AccountMigrationCheck(false) + + class AlsoKnownAsNotFound(val message: String) : AccountMigrationCheck(false) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainServiceImpl.kt new file mode 100644 index 00000000..f74bf530 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorMigrationCheckDomainServiceImpl.kt @@ -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.service.actor.local + +import dev.usbharu.hideout.core.domain.model.actor.Actor +import org.springframework.stereotype.Service + +@Service +class LocalActorMigrationCheckDomainServiceImpl : LocalActorMigrationCheckDomainService { + override suspend fun canAccountMigration(from: Actor, to: Actor): AccountMigrationCheck { + if (to == from) { + return AccountMigrationCheck.SelfReferences() + } + + if (to.moveTo != null) { + return AccountMigrationCheck.AlreadyMoved("${to.name}@${to.domain} was move to ${to.moveTo}") + } + + if (from.moveTo != null) { + return AccountMigrationCheck.AlreadyMoved("${from.name}@${from.domain} was move to ${from.moveTo}") + } + + if (to.alsoKnownAs.contains(to.id).not()) { + return AccountMigrationCheck.AlsoKnownAsNotFound("${to.id} has ${to.alsoKnownAs}") + } + + return AccountMigrationCheck.CanAccountMigration() + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actorinstancerelationship/ActorInstanceRelationshipDomainService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actorinstancerelationship/ActorInstanceRelationshipDomainService.kt new file mode 100644 index 00000000..d47ef39a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actorinstancerelationship/ActorInstanceRelationshipDomainService.kt @@ -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.domain.service.actorinstancerelationship + +interface ActorInstanceRelationshipDomainService { + suspend fun block() +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainService.kt new file mode 100644 index 00000000..97ec2cfb --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainService.kt @@ -0,0 +1,67 @@ +package dev.usbharu.hideout.core.domain.service.filter + +import dev.usbharu.hideout.core.domain.model.filter.Filter +import dev.usbharu.hideout.core.domain.model.filter.FilterContext +import dev.usbharu.hideout.core.domain.model.filter.FilterResult +import dev.usbharu.hideout.core.domain.model.filter.FilteredPost +import dev.usbharu.hideout.core.domain.model.post.Post +import org.springframework.stereotype.Service + +interface IFilterDomainService { + fun apply(post: Post, context: FilterContext, filters: List): FilteredPost + fun applyAll(postList: List, context: FilterContext, filters: List): List +} + +@Service +class FilterDomainService : IFilterDomainService { + override fun apply(post: Post, context: FilterContext, filters: List): FilteredPost { + val filterResults = filters + .filter { it.filterContext.contains(context) } + .flatMap { filter -> + val regex = filter.compileFilter() + post + .overview + ?.overview + ?.let { it1 -> regex.findAll(it1) } + .orEmpty() + .toList() + .map { FilterResult(filter, it.value) } + .plus( + post + .text + .let { regex.findAll(it) } + .toList() + .map { FilterResult(filter, it.value) } + ) + } + return FilteredPost(post, filterResults) + } + + override fun applyAll(postList: List, context: FilterContext, filters: List): List { + return filters + .filter { it.filterContext.contains(context) } + .map { it to it.compileFilter() } + .flatMap { compiledFilter -> + postList + .map { post -> + val filterResults = post + .overview + ?.overview + ?.let { overview -> compiledFilter.second.findAll(overview) } + .orEmpty() + .toList() + .map { FilterResult(compiledFilter.first, it.value) } + .plus( + post + .text + .let { compiledFilter.second.findAll(it) } + .toList() + .map { FilterResult(compiledFilter.first, it.value) } + ) + + post to filterResults + } + } + .map { FilteredPost(it.first, it.second) } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostContentFormatter.kt similarity index 98% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostContentFormatter.kt index bc203296..b1b0f86e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostContentFormatter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.post +package dev.usbharu.hideout.core.domain.service.post import org.jsoup.Jsoup import org.jsoup.nodes.Document diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostContentFormatter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/PostContentFormatter.kt similarity index 83% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostContentFormatter.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/PostContentFormatter.kt index 7a0269d5..e74b5f64 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostContentFormatter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/PostContentFormatter.kt @@ -14,8 +14,13 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.post +package dev.usbharu.hideout.core.domain.service.post interface PostContentFormatter { fun format(content: String): FormattedPostContent } + +data class FormattedPostContent( + val html: String, + val content: String, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt similarity index 59% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt index 0f1bd478..b59c0319 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt @@ -14,13 +14,20 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.notification +package dev.usbharu.hideout.core.domain.service.relationship import dev.usbharu.hideout.core.domain.model.relationship.Relationship import org.springframework.stereotype.Service @Service -class RelationshipNotificationManagementServiceImpl : RelationshipNotificationManagementService { - override fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean = - relationship.muting.not() +class RelationshipDomainService { + fun block(relationship: Relationship, inverseRelationship: Relationship) { + require(relationship != inverseRelationship) + require(relationship.actorId == inverseRelationship.targetActorId) + require(relationship.targetActorId == inverseRelationship.actorId) + + relationship.block() + inverseRelationship.unfollow() + inverseRelationship.unfollowRequest() + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/PasswordEncoder.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/PasswordEncoder.kt new file mode 100644 index 00000000..979bc1e0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/PasswordEncoder.kt @@ -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.domain.service.userdetail + +interface PasswordEncoder { + suspend fun encode(input: String): String +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainService.kt similarity index 66% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainService.kt index 6d394617..cb3fb074 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/init/MetaService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainService.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.service.init +package dev.usbharu.hideout.core.domain.service.userdetail -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.Meta +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword import org.springframework.stereotype.Service @Service -interface MetaService { - suspend fun getMeta(): Meta - suspend fun updateMeta(meta: Meta) - suspend fun getJwtMeta(): Jwt +class UserDetailDomainService(private val passwordEncoder: PasswordEncoder) { + suspend fun hashPassword(password: String) = UserDetailHashedPassword(passwordEncoder.encode(password)) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEvent.kt new file mode 100644 index 00000000..d4379335 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEvent.kt @@ -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.domain.shared.domainevent + +import java.time.Instant +import java.util.* + +/** + * エンティティで発生したドメインイベント + * + * @property id ID + * @property name ドメインイベント名 + * @property occurredOn 発生時刻 + * @property body ドメインイベントのボディ + * @property collectable trueで同じドメインイベント名でをまとめる + */ +data class DomainEvent( + val id: String, + val name: String, + val occurredOn: Instant, + val body: DomainEventBody, + val collectable: Boolean = false +) { + companion object { + fun create(name: String, body: DomainEventBody, collectable: Boolean = false): DomainEvent = + DomainEvent(UUID.randomUUID().toString(), name, Instant.now(), body, collectable) + + fun reconstruct( + id: String, + name: String, + occurredOn: Instant, + body: DomainEventBody, + collectable: Boolean + ): DomainEvent = DomainEvent(id, name, occurredOn, body, collectable) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventBody.kt similarity index 75% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventBody.kt index 2e3cbf1f..f4cd139d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventBody.kt @@ -14,13 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.model.nodeinfo +package dev.usbharu.hideout.core.domain.shared.domainevent -data class Nodeinfo( - val links: List -) { - data class Links( - val rel: String, - val href: String - ) +@Suppress("UnnecessaryAbstractClass") +abstract class DomainEventBody(val map: Map) { + fun toMap(): Map = map } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventPublisher.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventPublisher.kt new file mode 100644 index 00000000..7f7cd592 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventPublisher.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.core.domain.shared.domainevent + +interface DomainEventPublisher { + suspend fun publishEvent(domainEvent: DomainEvent) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/NotInitException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventStorable.kt similarity index 58% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/NotInitException.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventStorable.kt index 8118eb7e..6e6b83b7 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/exception/NotInitException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventStorable.kt @@ -14,18 +14,17 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.exception +package dev.usbharu.hideout.core.domain.shared.domainevent -import java.io.Serial +@Suppress("UnnecessaryAbstractClass") +abstract class DomainEventStorable { + private val domainEvents: MutableList = mutableListOf() -class NotInitException : IllegalStateException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = -5859046179473905716L + protected fun addDomainEvent(domainEvent: DomainEvent) { + domainEvents.add(domainEvent) } + + fun clearDomainEvents() = domainEvents.clear() + + fun getDomainEvents(): List = domainEvents.toList() } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventSubscriber.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventSubscriber.kt new file mode 100644 index 00000000..5d24a1b7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/domainevent/DomainEventSubscriber.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.domain.shared.domainevent + +interface DomainEventSubscriber { + fun subscribe(eventName: String, domainEventConsumer: DomainEventConsumer) +} + +typealias DomainEventConsumer = (DomainEvent) -> Unit diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/IdGenerateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/id/IdGenerateService.kt similarity index 86% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/IdGenerateService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/id/IdGenerateService.kt index ef2686e8..91991d71 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/IdGenerateService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/id/IdGenerateService.kt @@ -14,11 +14,8 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.service.id +package dev.usbharu.hideout.core.domain.shared.id -import org.springframework.stereotype.Service - -@Service interface IdGenerateService { suspend fun generateId(): Long } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/repository/DomainEventPublishableRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/repository/DomainEventPublishableRepository.kt new file mode 100644 index 00000000..3b385984 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/shared/repository/DomainEventPublishableRepository.kt @@ -0,0 +1,22 @@ +package dev.usbharu.hideout.core.domain.shared.repository + +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable +import org.springframework.stereotype.Repository + +@Repository +interface DomainEventPublishableRepository { + val domainEventPublisher: DomainEventPublisher + suspend fun update(entity: T) { + entity.getDomainEvents().distinctBy { + if (it.collectable) { + it.name + } else { + it.id + } + }.forEach { + domainEventPublisher.publishEvent(it) + } + entity.clearDomainEvents() + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverAcceptTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverAcceptTask.kt deleted file mode 100644 index b7b19949..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverAcceptTask.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.owl.common.property.* -import dev.usbharu.owl.common.task.PropertyDefinition -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class DeliverAcceptTask( - val accept: Accept, - val inbox: String, - val signer: Long, -) : Task() - -@Component -data object DeliverAcceptTaskDef : TaskDefinition { - override val name: String - get() = "DeliverAccept" - override val priority: Int - get() = 10 - override val maxRetry: Int - get() = 5 - override val retryPolicy: String - get() = "" - override val timeoutMilli: Long - get() = 1000 - override val propertyDefinition: PropertyDefinition - get() = PropertyDefinition( - mapOf( - "accept" to PropertyType.binary, - "inbox" to PropertyType.string, - "signer" to PropertyType.number, - ) - ) - override val type: Class - get() = DeliverAcceptTask::class.java - - override fun serialize(task: DeliverAcceptTask): Map> { - return mapOf( - "accept" to ObjectPropertyValue(task.accept), - "inbox" to StringPropertyValue(task.inbox), - "signer" to LongPropertyValue(task.signer) - ) - } - - override fun deserialize(value: Map>): DeliverAcceptTask { - return DeliverAcceptTask( - value.getValue("accept").value as Accept, - value.getValue("inbox").value as String, - value.getValue("signer").value as Long, - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverReactionTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverReactionTask.kt deleted file mode 100644 index c1c73154..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverReactionTask.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class DeliverReactionTask( - val actor: String, - val like: Like, - val inbox: String, -) : Task() - -@Component -data object DeliverReactionTaskDef : TaskDefinition { - override val type: Class - get() = DeliverReactionTask::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverRejectTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverRejectTask.kt deleted file mode 100644 index 5bb47432..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverRejectTask.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.domain.model.Reject -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class DeliverRejectTask( - val reject: Reject, - val inbox: String, - val signer: Long, -) : Task() - -@Component -data object DeliverRejectTaskDef : TaskDefinition { - override val type: Class - get() = DeliverRejectTask::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverUndoTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverUndoTask.kt deleted file mode 100644 index 3ae7f129..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverUndoTask.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.domain.model.Undo -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class DeliverUndoTask( - val undo: Undo, - val inbox: String, - val signer: Long, -) : Task() - -@Component -data object DeliverUndoTaskDef : TaskDefinition { - override val type: Class - get() = DeliverUndoTask::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/InboxTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/InboxTask.kt deleted file mode 100644 index de6b926f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/InboxTask.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.owl.common.property.ObjectPropertyValue -import dev.usbharu.owl.common.property.PropertyValue -import dev.usbharu.owl.common.property.StringPropertyValue -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class InboxTask( - val json: String, - val type: ActivityType, - val httpRequest: HttpRequest, - val headers: Map>, -) : Task() - -@Component -data object InboxTaskDef : TaskDefinition { - override val type: Class - get() = InboxTask::class.java - - override fun serialize(task: InboxTask): Map> { - return mapOf( - "json" to StringPropertyValue(task.json), - "type" to ObjectPropertyValue(task.type), - "httpRequest" to ObjectPropertyValue(task.httpRequest), - "headers" to ObjectPropertyValue(task.headers), - ) - } - - override fun deserialize(value: Map>): InboxTask { - return InboxTask( - value.getValue("json").value as String, - value.getValue("type").value as ActivityType, - value.getValue("httpRequest").value as HttpRequest, - value.getValue("headers").value as Map>, - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/ReceiveFollowTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/ReceiveFollowTask.kt deleted file mode 100644 index a72b0d5a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/ReceiveFollowTask.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.external.job - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.owl.common.property.ObjectPropertyValue -import dev.usbharu.owl.common.property.PropertyValue -import dev.usbharu.owl.common.property.StringPropertyValue -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition -import org.springframework.stereotype.Component - -data class ReceiveFollowTask( - val actor: String, - val follow: Follow, - val targetActor: String, -) : Task() - -@Component -data object ReceiveFollowTaskDef : TaskDefinition { - override val type: Class - get() = ReceiveFollowTask::class.java - - override fun serialize(task: ReceiveFollowTask): Map> { - return mapOf( - "actor" to StringPropertyValue(task.actor), - "follow" to ObjectPropertyValue(task.follow), - "targetActor" to StringPropertyValue(task.targetActor) - ) - } - - override fun deserialize(value: Map>): ReceiveFollowTask { - return ReceiveFollowTask( - value.getValue("actor").value as String, - value.getValue("follow").value as Follow, - value.getValue("targetActor").value as String, - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/DelegateMediaProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/DelegateMediaProcessor.kt new file mode 100644 index 00000000..65cbc14d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/DelegateMediaProcessor.kt @@ -0,0 +1,20 @@ +package dev.usbharu.hideout.core.external.media + +import dev.usbharu.hideout.core.domain.model.media.MimeType +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Component +import java.nio.file.Path + +@Component +@Qualifier("delegate") +class DelegateMediaProcessor( + private val fileTypeDeterminer: FileTypeDeterminer, + private val mediaProcessors: List +) : MediaProcessor { + override fun isSupported(mimeType: MimeType): Boolean = true + + override suspend fun process(path: Path, filename: String, mimeType: MimeType?): ProcessedMedia { + val fileType = fileTypeDeterminer.fileType(path, filename) + return mediaProcessors.first { it.isSupported(fileType) }.process(path, filename, fileType) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/FileTypeDeterminer.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/FileTypeDeterminer.kt new file mode 100644 index 00000000..83270bb6 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/FileTypeDeterminer.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.core.external.media + +import dev.usbharu.hideout.core.domain.model.media.MimeType +import java.nio.file.Path + +interface FileTypeDeterminer { + fun fileType(path: Path, filename: String): MimeType +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileTypeDeterminationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/MediaProcessor.kt similarity index 69% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileTypeDeterminationService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/MediaProcessor.kt index e09a263b..687aa718 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/FileTypeDeterminationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/MediaProcessor.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.external.media +import dev.usbharu.hideout.core.domain.model.media.MimeType import java.nio.file.Path -interface FileTypeDeterminationService { - fun fileType(byteArray: ByteArray, filename: String, contentType: String?): MimeType - fun fileType(path: Path, filename: String): MimeType +interface MediaProcessor { + fun isSupported(mimeType: MimeType): Boolean + suspend fun process(path: Path, filename: String, mimeType: MimeType?): ProcessedMedia } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/ProcessedMedia.kt similarity index 69% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/ProcessedMedia.kt index 386daa78..b69ece17 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/ProcessedMedia.kt @@ -14,12 +14,16 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.service.media +package dev.usbharu.hideout.core.external.media -import dev.usbharu.hideout.core.domain.model.media.Media -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest +import dev.usbharu.hideout.core.domain.model.media.FileType +import dev.usbharu.hideout.core.domain.model.media.MimeType +import java.nio.file.Path -interface MediaService { - suspend fun uploadLocalMedia(mediaRequest: MediaRequest): Media - suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media -} +data class ProcessedMedia( + val path: Path, + val thumbnailPath: Path?, + val fileType: FileType, + val mimeType: MimeType, + val blurHash: String?, +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/TikaFileTypeDeterminer.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/TikaFileTypeDeterminer.kt new file mode 100644 index 00000000..393798b7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/media/TikaFileTypeDeterminer.kt @@ -0,0 +1,51 @@ +package dev.usbharu.hideout.core.external.media + +import dev.usbharu.hideout.core.domain.model.media.FileType +import dev.usbharu.hideout.core.domain.model.media.MimeType +import org.apache.tika.Tika +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component +import java.nio.file.Path + +@Component +class TikaFileTypeDeterminer : FileTypeDeterminer { + override fun fileType(path: Path, filename: String): MimeType { + logger.info("START Detect file type name: {}", filename) + + val tika = Tika() + + val detect = try { + tika.detect(path) + } catch (e: IllegalStateException) { + logger.warn("FAILED Detect file type", e) + "application/octet-stream" + } + + val type = detect.substringBefore("/") + val fileType = when (type) { + "image" -> { + FileType.Image + } + + "video" -> { + FileType.Video + } + + "audio" -> { + FileType.Audio + } + + else -> { + FileType.Unknown + } + } + val mimeType = MimeType(type, detect.substringAfter("/"), fileType) + + logger.info("SUCCESS Detect file type name: {},MimeType: {}", filename, mimeType) + return mimeType + } + + companion object { + private val logger = LoggerFactory.getLogger(TikaFileTypeDeterminer::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/mediastore/MediaStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/mediastore/MediaStore.kt new file mode 100644 index 00000000..d2ee30d1 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/mediastore/MediaStore.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.core.external.mediastore + +import java.net.URI +import java.nio.file.Path + +interface MediaStore { + suspend fun upload(path: Path, id: String): URI +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/awss3/AWSS3MediaStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/awss3/AWSS3MediaStore.kt new file mode 100644 index 00000000..47e30858 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/awss3/AWSS3MediaStore.kt @@ -0,0 +1,50 @@ +package dev.usbharu.hideout.core.infrastructure.awss3 + +import dev.usbharu.hideout.core.config.S3StorageConfig +import dev.usbharu.hideout.core.external.mediastore.MediaStore +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component +import software.amazon.awssdk.core.sync.RequestBody +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.PutObjectRequest +import java.net.URI +import java.nio.file.Path + +@Component +@ConditionalOnProperty("hideout.storage.type", havingValue = "s3") +class AWSS3MediaStore( + private val s3StorageConfig: S3StorageConfig, + private val s3Client: S3Client +) : MediaStore { + override suspend fun upload(path: Path, id: String): URI { + logger.info("MEDIA upload. {}", id) + + val fileUploadRequest = PutObjectRequest.builder() + .bucket(s3StorageConfig.bucket) + .key(id) + .build() + + logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, id) + + withContext(Dispatchers.IO) { + s3Client.putObject(fileUploadRequest, RequestBody.fromFile(path)) + } + val successSavedMedia = URI.create("${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$id") + + logger.info("SUCCESS Media upload. {}", id) + logger.debug( + "name: {} url: {}", + id, + successSavedMedia, + ) + + return successSavedMedia + } + + companion object { + private val logger = LoggerFactory.getLogger(AWSS3MediaStore::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt new file mode 100644 index 00000000..8b8e46e8 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt @@ -0,0 +1,51 @@ +/* + * 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.infrastructure.exposed + +import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors +import dev.usbharu.hideout.core.infrastructure.exposedrepository.ActorsAlsoKnownAs +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.ResultRow +import org.springframework.stereotype.Component + +@Component +class ActorQueryMapper(private val actorResultRowMapper: ResultRowMapper) : QueryMapper { + override fun map(query: Query): List { + return query + .groupBy { it[Actors.id] } + .map { it.value } + .map { + it + .first() + .let(actorResultRowMapper::map) + .apply { + alsoKnownAs = it.mapNotNull { resultRow: ResultRow -> + resultRow.getOrNull( + ActorsAlsoKnownAs.alsoKnownAs + )?.let { actorId -> + ActorId( + actorId + ) + } + }.toSet() + clearDomainEvents() + } + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt new file mode 100644 index 00000000..3bb58e43 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt @@ -0,0 +1,68 @@ +/* + * 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.infrastructure.exposed + +import dev.usbharu.hideout.core.domain.model.actor.* +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId +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.shared.Domain +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors +import org.jetbrains.exposed.sql.ResultRow +import org.springframework.stereotype.Component +import java.net.URI + +@Component +class ActorResultRowMapper : ResultRowMapper { + override fun map(resultRow: ResultRow): Actor { + return Actor( + id = ActorId(resultRow[Actors.id]), + name = ActorName(resultRow[Actors.name]), + domain = Domain(resultRow[Actors.domain]), + screenName = ActorScreenName(resultRow[Actors.screenName]), + description = ActorDescription(resultRow[Actors.description]), + inbox = URI.create(resultRow[Actors.inbox]), + outbox = URI.create(resultRow[Actors.outbox]), + url = URI.create(resultRow[Actors.url]), + publicKey = ActorPublicKey(resultRow[Actors.publicKey]), + privateKey = resultRow[Actors.privateKey]?.let { ActorPrivateKey(it) }, + createdAt = resultRow[Actors.createdAt], + keyId = ActorKeyId(resultRow[Actors.keyId]), + followersEndpoint = resultRow[Actors.followers]?.let { URI.create(it) }, + followingEndpoint = resultRow[Actors.following]?.let { URI.create(it) }, + instance = InstanceId(resultRow[Actors.instance]), + locked = resultRow[Actors.locked], + followersCount = resultRow[Actors.followersCount]?.let { ActorRelationshipCount(it) }, + followingCount = resultRow[Actors.followingCount]?.let { ActorRelationshipCount(it) }, + postsCount = ActorPostsCount(resultRow[Actors.postsCount]), + lastPostAt = resultRow[Actors.lastPostAt], + suspend = resultRow[Actors.suspend], + lastUpdateAt = resultRow[Actors.lastUpdateAt], + alsoKnownAs = emptySet(), + moveTo = resultRow[Actors.moveTo]?.let { ActorId(it) }, + emojiIds = resultRow[Actors.emojis] + .split(",") + .filter { it.isNotEmpty() } + .map { EmojiId(it.toLong()) } + .toSet(), + deleted = resultRow[Actors.deleted], + roles = emptySet(), + icon = resultRow[Actors.icon]?.let { MediaId(it) }, + banner = resultRow[Actors.banner]?.let { MediaId(it) } + ) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ExposedTransaction.kt similarity index 75% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ExposedTransaction.kt index bdc84fba..7dc419f3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedTransaction.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ExposedTransaction.kt @@ -14,28 +14,27 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.infrastructure.exposed +package dev.usbharu.hideout.core.infrastructure.exposed -import dev.usbharu.hideout.application.external.Transaction -import kotlinx.coroutines.runBlocking +import dev.usbharu.hideout.core.application.shared.Transaction import kotlinx.coroutines.slf4j.MDCContext import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.jetbrains.exposed.sql.transactions.transaction -import org.springframework.stereotype.Service +import org.springframework.stereotype.Component import java.sql.Connection -@Service +@Component class ExposedTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T { - return transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) { + return newSuspendedTransaction( + transactionIsolation = Connection.TRANSACTION_READ_COMMITTED, + context = MDCContext() + ) { debug = true warnLongQueriesDuration = 1000 addLogger(Slf4jSqlDebugLogger) - runBlocking(MDCContext()) { - block() - } + block() } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt new file mode 100644 index 00000000..5b6039c4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt @@ -0,0 +1,53 @@ +/* + * 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.infrastructure.exposed + +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.infrastructure.exposedrepository.FilterKeywords +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.ResultRow +import org.springframework.stereotype.Component + +@Component +class FilterQueryMapper(private val filterResultRowMapper: ResultRowMapper) : QueryMapper { + override fun map(query: Query): List { + return query + .groupBy { it[Filters.id] } + .map { it.value } + .map { + it + .first() + .let(filterResultRowMapper::map) + .apply { + reconstructWith( + it.mapNotNull { resultRow: ResultRow -> + FilterKeyword( + FilterKeywordId(resultRow.getOrNull(FilterKeywords.id) ?: return@mapNotNull null), + FilterKeywordKeyword( + resultRow.getOrNull(FilterKeywords.keyword) ?: return@mapNotNull null + ), + FilterMode.valueOf( + resultRow.getOrNull(FilterKeywords.mode) ?: return@mapNotNull null + ) + ) + }.toSet() + ) + } + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt similarity index 50% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt index 2c976d4c..e668f045 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterResultRowMapper.kt @@ -16,13 +16,20 @@ package dev.usbharu.hideout.core.infrastructure.exposed -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.actor.Actor -import org.jetbrains.exposed.sql.Query +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters +import org.jetbrains.exposed.sql.ResultRow import org.springframework.stereotype.Component @Component -class UserQueryMapper(private val actorResultRowMapper: ResultRowMapper) : QueryMapper { - override fun map(query: Query): List = query.map(actorResultRowMapper::map) +class FilterResultRowMapper : ResultRowMapper { + override fun map(resultRow: ResultRow): Filter = Filter( + FilterId(resultRow[Filters.id]), + UserDetailId(resultRow[Filters.userId]), + FilterName(resultRow[Filters.name]), + resultRow[Filters.context].split(",").filter { it.isNotEmpty() }.map { FilterContext.valueOf(it) }.toSet(), + FilterAction.valueOf(resultRow[Filters.filterAction]), + emptySet() + ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt index c9598fb1..1ea7e1b8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt @@ -16,26 +16,48 @@ package dev.usbharu.hideout.core.infrastructure.exposed -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId +import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsEmojis import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsMedia +import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsVisibleActors import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.ResultRow import org.springframework.stereotype.Component @Component class PostQueryMapper(private val postResultRowMapper: ResultRowMapper) : QueryMapper { override fun map(query: Query): List { - return query.groupBy { it[Posts.id] } + return query + .groupBy { it[Posts.id] } .map { it.value } .map { - it.first().let(postResultRowMapper::map) - .copy( - mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) }, - emojiIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsEmojis.emojiId) } - ) + it + .first() + .let(postResultRowMapper::map) + .apply { + reconstructWith( + mediaIds = it.mapNotNull { resultRow: ResultRow -> + resultRow + .getOrNull(PostsMedia.mediaId) + ?.let { mediaId -> MediaId(mediaId) } + }, + emojis = it + .mapNotNull { resultRow: ResultRow -> + resultRow + .getOrNull(PostsEmojis.emojiId) + ?.let { emojiId -> EmojiId(emojiId) } + }, + visibleActors = it.mapNotNull { resultRow: ResultRow -> + resultRow + .getOrNull(PostsVisibleActors.actorId) + ?.let { actorId -> ActorId(actorId) } + }.toSet() + ) + } } } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt index b18690e3..0fb4c803 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt @@ -16,29 +16,33 @@ package dev.usbharu.hideout.core.infrastructure.exposed -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.post.* import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts import org.jetbrains.exposed.sql.ResultRow import org.springframework.stereotype.Component +import java.net.URI @Component -class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper { +class PostResultRowMapper : ResultRowMapper { override fun map(resultRow: ResultRow): Post { - return postBuilder.of( - id = resultRow[Posts.id], - actorId = resultRow[Posts.actorId], - overview = resultRow[Posts.overview], - content = resultRow[Posts.text], + return Post( + id = PostId(resultRow[Posts.id]), + actorId = ActorId(resultRow[Posts.actorId]), + overview = resultRow[Posts.overview]?.let { PostOverview(it) }, + content = PostContent(resultRow[Posts.text], resultRow[Posts.content], emptyList()), createdAt = resultRow[Posts.createdAt], - visibility = Visibility.values().first { visibility -> visibility.ordinal == resultRow[Posts.visibility] }, - url = resultRow[Posts.url], - repostId = resultRow[Posts.repostId], - replyId = resultRow[Posts.replyId], + visibility = Visibility.valueOf(resultRow[Posts.visibility]), + url = URI.create(resultRow[Posts.url]), + repostId = resultRow[Posts.repostId]?.let { PostId(it) }, + replyId = resultRow[Posts.replyId]?.let { PostId(it) }, sensitive = resultRow[Posts.sensitive], - apId = resultRow[Posts.apId], + apId = URI.create(resultRow[Posts.apId]), deleted = resultRow[Posts.deleted], + mediaIds = emptyList(), + visibleActors = emptySet(), + hide = resultRow[Posts.hide], + moveTo = resultRow[Posts.moveTo]?.let { PostId(it) } ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/QueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/QueryMapper.kt similarity index 91% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/QueryMapper.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/QueryMapper.kt index 078fdd65..a817b9b2 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/QueryMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/QueryMapper.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.infrastructure.exposed +package dev.usbharu.hideout.core.infrastructure.exposed import org.jetbrains.exposed.sql.Query diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ResultRowMapper.kt similarity index 91% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ResultRowMapper.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ResultRowMapper.kt index cc7737ff..7f4b3261 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ResultRowMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ResultRowMapper.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.infrastructure.exposed +package dev.usbharu.hideout.core.infrastructure.exposed import org.jetbrains.exposed.sql.ResultRow diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt deleted file mode 100644 index 2f6a02d2..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.infrastructure.exposed - -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import org.jetbrains.exposed.sql.ResultRow -import org.springframework.stereotype.Component -import java.time.Instant - -@Component -class UserResultRowMapper(private val actorBuilder: Actor.UserBuilder) : ResultRowMapper { - override fun map(resultRow: ResultRow): Actor { - return actorBuilder.of( - id = resultRow[Actors.id], - name = resultRow[Actors.name], - domain = resultRow[Actors.domain], - screenName = resultRow[Actors.screenName], - description = resultRow[Actors.description], - inbox = resultRow[Actors.inbox], - outbox = resultRow[Actors.outbox], - url = resultRow[Actors.url], - publicKey = resultRow[Actors.publicKey], - privateKey = resultRow[Actors.privateKey], - createdAt = Instant.ofEpochMilli((resultRow[Actors.createdAt])), - keyId = resultRow[Actors.keyId], - followers = resultRow[Actors.followers], - following = resultRow[Actors.following], - instance = resultRow[Actors.instance], - locked = resultRow[Actors.locked], - followingCount = resultRow[Actors.followingCount], - followersCount = resultRow[Actors.followersCount], - postsCount = resultRow[Actors.postsCount], - lastPostDate = resultRow[Actors.lastPostAt], - emojis = resultRow[Actors.emojis].split(",").filter { it.isNotEmpty() }.map { it.toLong() } - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt deleted file mode 100644 index 770ad559..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.infrastructure.exposedquery - -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.relationship.RelationshipRepository -import dev.usbharu.hideout.core.query.FollowerQueryService -import org.springframework.stereotype.Repository - -@Repository -class FollowerQueryServiceImpl( - private val relationshipRepository: RelationshipRepository, - private val actorRepository: ActorRepository -) : FollowerQueryService { - override suspend fun findFollowersById(id: Long): List { - return actorRepository.findByIds( - relationshipRepository.findByTargetIdAndFollowing(id, true).map { it.actorId } - ) - } - - override suspend fun alreadyFollow(actorId: Long, followerId: Long): Boolean = - relationshipRepository.findByUserIdAndTargetUserId(followerId, actorId)?.following ?: false -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt deleted file mode 100644 index 08bc5de4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ActorRepositoryImpl( - private val idGenerateService: IdGenerateService, - private val actorResultRowMapper: ResultRowMapper, - private val actorQueryMapper: QueryMapper -) : ActorRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(actor: Actor): Actor = query { - val singleOrNull = Actors.selectAll().where { Actors.id eq actor.id }.forUpdate().empty() - if (singleOrNull) { - Actors.insert { - it[id] = actor.id - it[name] = actor.name - it[domain] = actor.domain - it[screenName] = actor.screenName - it[description] = actor.description - it[inbox] = actor.inbox - it[outbox] = actor.outbox - it[url] = actor.url - it[createdAt] = actor.createdAt.toEpochMilli() - it[publicKey] = actor.publicKey - it[privateKey] = actor.privateKey - it[keyId] = actor.keyId - it[following] = actor.following - it[followers] = actor.followers - it[instance] = actor.instance - it[locked] = actor.locked - it[followersCount] = actor.followersCount - it[followingCount] = actor.followingCount - it[postsCount] = actor.postsCount - it[lastPostAt] = actor.lastPostDate - it[emojis] = actor.emojis.joinToString(",") - } - } else { - Actors.update({ Actors.id eq actor.id }) { - it[name] = actor.name - it[domain] = actor.domain - it[screenName] = actor.screenName - it[description] = actor.description - it[inbox] = actor.inbox - it[outbox] = actor.outbox - it[url] = actor.url - it[createdAt] = actor.createdAt.toEpochMilli() - it[publicKey] = actor.publicKey - it[privateKey] = actor.privateKey - it[keyId] = actor.keyId - it[following] = actor.following - it[followers] = actor.followers - it[instance] = actor.instance - it[locked] = actor.locked - it[followersCount] = actor.followersCount - it[followingCount] = actor.followingCount - it[postsCount] = actor.postsCount - it[lastPostAt] = actor.lastPostDate - it[emojis] = actor.emojis.joinToString(",") - } - } - return@query actor - } - - override suspend fun findById(id: Long): Actor? = query { - return@query Actors.selectAll().where { Actors.id eq id }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun findByIdWithLock(id: Long): Actor? = query { - return@query Actors.selectAll().where { Actors.id eq id }.forUpdate().singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findAll(limit: Int, offset: Long): List = query { - return@query Actors.selectAll().limit(limit, offset).let(actorQueryMapper::map) - } - - override suspend fun findByName(name: String): List = query { - return@query Actors.selectAll().where { Actors.name eq name }.let(actorQueryMapper::map) - } - - override suspend fun findByNameAndDomain(name: String, domain: String): Actor? = query { - return@query Actors.selectAll().where { Actors.name eq name and (Actors.domain eq domain) }.singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor? = query { - return@query Actors.selectAll().where { Actors.name eq name and (Actors.domain eq domain) }.forUpdate() - .singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByUrl(url: String): Actor? = query { - return@query Actors.selectAll().where { Actors.url eq url }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun findByUrlWithLock(url: String): Actor? = query { - return@query Actors.selectAll().where { Actors.url eq url }.forUpdate().singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByIds(ids: List): List = query { - return@query Actors.selectAll().where { Actors.id inList ids }.let(actorQueryMapper::map) - } - - override suspend fun findByKeyId(keyId: String): Actor? = query { - return@query Actors.selectAll().where { Actors.keyId eq keyId }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun delete(id: Long): Unit = query { - Actors.deleteWhere { Actors.id.eq(id) } - } - - override suspend fun nextId(): Long = idGenerateService.generateId() - - companion object { - private val logger = LoggerFactory.getLogger(ActorRepositoryImpl::class.java) - } -} - -object Actors : Table("actors") { - val id: Column = long("id") - val name: Column = varchar("name", length = 300) - val domain: Column = varchar("domain", length = 1000) - val screenName: Column = varchar("screen_name", length = 300) - val description: Column = varchar( - "description", - length = 10000 - ) - val inbox: Column = varchar("inbox", length = 1000).uniqueIndex() - val outbox: Column = varchar("outbox", length = 1000).uniqueIndex() - val url: Column = varchar("url", length = 1000).uniqueIndex() - val publicKey: Column = varchar("public_key", length = 10000) - val privateKey: Column = varchar( - "private_key", - length = 10000 - ).nullable() - val createdAt: Column = long("created_at") - val keyId = varchar("key_id", length = 1000) - val following = varchar("following", length = 1000).nullable() - val followers = varchar("followers", length = 1000).nullable() - val instance = long("instance").references(Instance.id) - val locked = bool("locked") - val followingCount = integer("following_count") - val followersCount = integer("followers_count") - val postsCount = integer("posts_count") - val lastPostAt = timestamp("last_post_at").nullable() - val emojis = varchar("emojis", 3000) - override val primaryKey: PrimaryKey = PrimaryKey(id) - - init { - uniqueIndex(name, domain) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt index a73378ad..9af0e090 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt @@ -16,9 +16,11 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository -import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.shared.Domain import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.javatime.CurrentTimestamp @@ -26,34 +28,33 @@ import org.jetbrains.exposed.sql.javatime.timestamp import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository +import java.net.URI @Repository -class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService) : CustomEmojiRepository, +class CustomEmojiRepositoryImpl : CustomEmojiRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger - override suspend fun generateId(): Long = idGenerateService.generateId() - override suspend fun save(customEmoji: CustomEmoji): CustomEmoji = query { val singleOrNull = - CustomEmojis.selectAll().where { CustomEmojis.id eq customEmoji.id }.forUpdate().singleOrNull() + CustomEmojis.selectAll().where { CustomEmojis.id eq customEmoji.id.emojiId }.forUpdate().singleOrNull() if (singleOrNull == null) { CustomEmojis.insert { - it[id] = customEmoji.id + it[id] = customEmoji.id.emojiId it[name] = customEmoji.name - it[domain] = customEmoji.domain - it[instanceId] = customEmoji.instanceId - it[url] = customEmoji.url + it[domain] = customEmoji.domain.domain + it[instanceId] = customEmoji.instanceId.instanceId + it[url] = customEmoji.url.toString() it[category] = customEmoji.category it[createdAt] = customEmoji.createdAt } } else { - CustomEmojis.update({ CustomEmojis.id eq customEmoji.id }) { + CustomEmojis.update({ CustomEmojis.id eq customEmoji.id.emojiId }) { it[name] = customEmoji.name - it[domain] = customEmoji.domain - it[instanceId] = customEmoji.instanceId - it[url] = customEmoji.url + it[domain] = customEmoji.domain.domain + it[instanceId] = customEmoji.instanceId.instanceId + it[url] = customEmoji.url.toString() it[category] = customEmoji.category it[createdAt] = customEmoji.createdAt } @@ -66,14 +67,25 @@ class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService } override suspend fun delete(customEmoji: CustomEmoji): Unit = query { - CustomEmojis.deleteWhere { id eq customEmoji.id } + CustomEmojis.deleteWhere { id eq customEmoji.id.emojiId } } - override suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji? = query { + override suspend fun findByNamesAndDomain(names: List, domain: String): List = query { return@query CustomEmojis - .selectAll().where { CustomEmojis.name eq name and (CustomEmojis.domain eq domain) } - .singleOrNull() - ?.toCustomEmoji() + .selectAll() + .where { + CustomEmojis.name inList names and (CustomEmojis.domain eq domain) + } + .map { it.toCustomEmoji() } + } + + override suspend fun findByIds(ids: List): List = query { + return@query CustomEmojis + .selectAll() + .where { + CustomEmojis.id inList ids + } + .map { it.toCustomEmoji() } } companion object { @@ -82,22 +94,22 @@ class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService } fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji( - id = this[CustomEmojis.id], + id = EmojiId(this[CustomEmojis.id]), name = this[CustomEmojis.name], - domain = this[CustomEmojis.domain], - instanceId = this[CustomEmojis.instanceId], - url = this[CustomEmojis.url], + domain = Domain(this[CustomEmojis.domain]), + instanceId = InstanceId(this[CustomEmojis.instanceId]), + url = URI.create(this[CustomEmojis.url]), category = this[CustomEmojis.category], createdAt = this[CustomEmojis.createdAt] ) fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? { return CustomEmoji( - id = this.getOrNull(CustomEmojis.id) ?: return null, + id = EmojiId(this.getOrNull(CustomEmojis.id) ?: return null), name = this.getOrNull(CustomEmojis.name) ?: return null, - domain = this.getOrNull(CustomEmojis.domain) ?: return null, - instanceId = this[CustomEmojis.instanceId], - url = this.getOrNull(CustomEmojis.url) ?: return null, + domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null), + instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null), + url = URI.create(this.getOrNull(CustomEmojis.url) ?: return null), category = this[CustomEmojis.category], createdAt = this.getOrNull(CustomEmojis.createdAt) ?: return null ) @@ -107,7 +119,7 @@ object CustomEmojis : Table("emojis") { val id = long("id") val name = varchar("name", 1000) val domain = varchar("domain", 1000) - val instanceId = long("instance_id").references(Instance.id).nullable() + val instanceId = long("instance_id").references(Instance.id) val url = varchar("url", 255).uniqueIndex() val category = varchar("category", 255).nullable() val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt deleted file mode 100644 index 41bd924b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor -import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(deletedActor: DeletedActor): DeletedActor = query { - val singleOrNull = - DeletedActors.selectAll().where { DeletedActors.id eq deletedActor.id }.forUpdate().singleOrNull() - - if (singleOrNull == null) { - DeletedActors.insert { - it[id] = deletedActor.id - it[name] = deletedActor.name - it[domain] = deletedActor.domain - it[apId] = deletedActor.apiId - it[publicKey] = deletedActor.publicKey - it[deletedAt] = deletedActor.deletedAt - } - } else { - DeletedActors.update({ DeletedActors.id eq deletedActor.id }) { - it[name] = deletedActor.name - it[domain] = deletedActor.domain - it[apId] = deletedActor.apiId - it[publicKey] = deletedActor.publicKey - it[deletedAt] = deletedActor.deletedAt - } - } - return@query deletedActor - } - - override suspend fun delete(deletedActor: DeletedActor): Unit = query { - DeletedActors.deleteWhere { id eq deletedActor.id } - } - - override suspend fun findById(id: Long): DeletedActor? = query { - return@query DeletedActors - .selectAll().where { DeletedActors.id eq id } - .singleOrNull() - ?.toDeletedActor() - } - - override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor? = query { - return@query DeletedActors - .selectAll().where { DeletedActors.name eq name and (DeletedActors.domain eq domain) } - .singleOrNull() - ?.toDeletedActor() - } - - companion object { - private val logger = LoggerFactory.getLogger(DeletedActorRepositoryImpl::class.java) - } -} - -fun ResultRow.toDeletedActor(): DeletedActor = deletedActor(this) - -private fun deletedActor(singleOr: ResultRow): DeletedActor { - return DeletedActor( - id = singleOr[DeletedActors.id], - name = singleOr[DeletedActors.name], - domain = singleOr[DeletedActors.domain], - apiId = singleOr[DeletedActors.publicKey], - publicKey = singleOr[DeletedActors.apId], - deletedAt = singleOr[DeletedActors.deletedAt] - ) -} - -object DeletedActors : Table("deleted_actors") { - val id = long("id") - val name = varchar("name", 300) - val domain = varchar("domain", 255) - val apId = varchar("ap_id", 255).uniqueIndex() - val publicKey = varchar("public_key", 10000).uniqueIndex() - val deletedAt = timestamp("deleted_at") - override val primaryKey: PrimaryKey = PrimaryKey(id) - - init { - uniqueIndex(name, domain) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt new file mode 100644 index 00000000..ab7ab447 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt @@ -0,0 +1,100 @@ +/* + * 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.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +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.instance.InstanceId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedActorInstanceRelationshipRepository(override val domainEventPublisher: DomainEventPublisher) : + ActorInstanceRelationshipRepository, + AbstractRepository(), + DomainEventPublishableRepository { + override val logger: Logger + get() = Companion.logger + + override suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship { + query { + ActorInstanceRelationships.upsert { + it[actorId] = actorInstanceRelationship.actorId.id + it[instanceId] = actorInstanceRelationship.instanceId.instanceId + it[blocking] = actorInstanceRelationship.isBlocking() + it[muting] = actorInstanceRelationship.isMuting() + it[doNotSendPrivate] = actorInstanceRelationship.isDoNotSendPrivate() + } + } + update(actorInstanceRelationship) + return actorInstanceRelationship + } + + override suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship) { + query { + ActorInstanceRelationships.deleteWhere { + actorId eq actorInstanceRelationship.actorId.id and + (instanceId eq actorInstanceRelationship.instanceId.instanceId) + } + } + update(actorInstanceRelationship) + } + + override suspend fun findByActorIdAndInstanceId( + actorId: ActorId, + instanceId: InstanceId, + ): ActorInstanceRelationship? = query { + ActorInstanceRelationships + .selectAll() + .where { + ActorInstanceRelationships.actorId eq actorId.id and + (ActorInstanceRelationships.instanceId eq instanceId.instanceId) + } + .singleOrNull() + ?.toActorInstanceRelationship() + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedActorInstanceRelationshipRepository::class.java) + } +} + +private fun ResultRow.toActorInstanceRelationship(): ActorInstanceRelationship { + return ActorInstanceRelationship( + actorId = ActorId(this[ActorInstanceRelationships.actorId]), + instanceId = InstanceId(this[ActorInstanceRelationships.instanceId]), + blocking = this[ActorInstanceRelationships.blocking], + muting = this[ActorInstanceRelationships.muting], + doNotSendPrivate = this[ActorInstanceRelationships.doNotSendPrivate], + ) +} + +object ActorInstanceRelationships : Table("actor_instance_relationships") { + val actorId = long("actor_id").references(Actors.id) + val instanceId = long("instance_id").references(Instance.id) + val blocking = bool("blocking") + val muting = bool("muting") + val doNotSendPrivate = bool("do_not_send_private") + + override val primaryKey: PrimaryKey = PrimaryKey(actorId, instanceId) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt new file mode 100644 index 00000000..8e184021 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt @@ -0,0 +1,148 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.* +import dev.usbharu.hideout.core.domain.model.shared.Domain +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository +import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedActorRepository( + private val actorQueryMapper: QueryMapper, + override val domainEventPublisher: DomainEventPublisher, +) : AbstractRepository(), + DomainEventPublishableRepository, + ActorRepository { + override val logger: Logger + get() = Companion.logger + + override suspend fun save(actor: Actor): Actor { + query { + Actors.upsert { + it[id] = actor.id.id + it[name] = actor.name.name + it[domain] = actor.domain.domain + it[screenName] = actor.screenName.screenName + it[description] = actor.description.description + it[inbox] = actor.inbox.toString() + it[outbox] = actor.outbox.toString() + it[url] = actor.outbox.toString() + it[publicKey] = actor.publicKey.publicKey + it[privateKey] = actor.privateKey?.privateKey + it[createdAt] = actor.createdAt + it[keyId] = actor.keyId.keyId + it[following] = actor.followingEndpoint?.toString() + it[followers] = actor.followersEndpoint?.toString() + it[instance] = actor.instance.instanceId + it[locked] = actor.locked + it[followingCount] = actor.followingCount?.relationshipCount + it[followersCount] = actor.followersCount?.relationshipCount + it[postsCount] = actor.postsCount.postsCount + it[lastPostAt] = actor.lastPostAt + it[lastUpdateAt] = actor.lastUpdateAt + it[suspend] = actor.suspend + it[moveTo] = actor.moveTo?.id + it[emojis] = actor.emojis.joinToString(",") + it[icon] = actor.icon?.id + it[banner] = actor.banner?.id + } + ActorsAlsoKnownAs.deleteWhere { + actorId eq actor.id.id + } + ActorsAlsoKnownAs.batchInsert(actor.alsoKnownAs) { + this[ActorsAlsoKnownAs.actorId] = actor.id.id + this[ActorsAlsoKnownAs.alsoKnownAs] = it.id + } + } + update(actor) + return actor + } + + override suspend fun delete(actor: Actor) { + query { + Actors.deleteWhere { id eq actor.id.id } + ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id } + } + update(actor) + } + + override suspend fun findById(id: ActorId): Actor? { + return query { + Actors + .leftJoin(ActorsAlsoKnownAs, onColumn = { Actors.id }, otherColumn = { actorId }) + .selectAll() + .where { + Actors.id eq id.id + } + .let(actorQueryMapper::map) + .firstOrNull() + } + } + + override suspend fun findByNameAndDomain(name: String, domain: String): Actor? { + return query { + Actors + .leftJoin(ActorsAlsoKnownAs, onColumn = { id }, otherColumn = { actorId }) + .selectAll() + .where { + Actors.name eq name and (Actors.domain eq domain) + } + .let(actorQueryMapper::map) + .firstOrNull() + } + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedActorRepository::class.java) + } +} + +object Actors : Table("actors") { + val id = long("id") + val name = varchar("name", ActorName.length) + val domain = varchar("domain", Domain.LENGTH) + val screenName = varchar("screen_name", ActorScreenName.length) + val description = varchar("description", ActorDescription.length) + val inbox = varchar("inbox", 1000).uniqueIndex() + val outbox = varchar("outbox", 1000).uniqueIndex() + val url = varchar("url", 1000).uniqueIndex() + val publicKey = varchar("public_key", 10000) + val privateKey = varchar("private_key", 100000).nullable() + val createdAt = timestamp("created_at") + val keyId = varchar("key_id", 1000) + val following = varchar("following", 1000).nullable() + val followers = varchar("followers", 1000).nullable() + val instance = long("instance").references(Instance.id) + val locked = bool("locked") + val followingCount = integer("following_count").nullable() + val followersCount = integer("followers_count").nullable() + val postsCount = integer("posts_count") + val lastPostAt = timestamp("last_post_at").nullable() + val lastUpdateAt = timestamp("last_update_at") + val suspend = bool("suspend") + val moveTo = long("move_to").references(id).nullable() + val emojis = varchar("emojis", 3000) + val deleted = bool("deleted") + val banner = long("banner").references(Media.id).nullable() + val icon = long("icon").references(Media.id).nullable() + + override val primaryKey = PrimaryKey(id) + + init { + uniqueIndex(name, domain) + } +} + +object ActorsAlsoKnownAs : Table("actor_alsoknownas") { + val actorId = + long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) + val alsoKnownAs = long("also_known_as").references(Actors.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + + override val primaryKey: PrimaryKey = PrimaryKey(actorId, alsoKnownAs) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepository.kt new file mode 100644 index 00000000..42d5c460 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepository.kt @@ -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.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.application.Application +import dev.usbharu.hideout.core.domain.model.application.ApplicationRepository +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.upsert +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedApplicationRepository : ApplicationRepository, AbstractRepository() { + override val logger: Logger + get() = Companion.logger + + override suspend fun save(application: Application) = query { + Applications.upsert { + it[id] = application.applicationId.id + it[name] = application.name.name + } + application + } + + override suspend fun delete(application: Application): Unit = query { + Applications.deleteWhere { id eq application.applicationId.id } + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedApplicationRepository::class.java) + } +} + +object Applications : Table("applications") { + val id = long("id") + val name = varchar("name", 500) + override val primaryKey: PrimaryKey = PrimaryKey(id) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterKeywordRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterKeywordRepository.kt deleted file mode 100644 index 23d6f0c8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterKeywordRepository.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.filter.FilterMode -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ExposedFilterKeywordRepository(private val idGenerateService: IdGenerateService) : FilterKeywordRepository, - AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(filterKeyword: FilterKeyword): FilterKeyword = query { - val empty = FilterKeywords.selectAll().where { FilterKeywords.id eq filterKeyword.id }.empty() - if (empty) { - FilterKeywords.insert { - it[id] = filterKeyword.id - it[filterId] = filterKeyword.filterId - it[keyword] = filterKeyword.keyword - it[mode] = filterKeyword.mode.name - } - } else { - FilterKeywords.update({ FilterKeywords.id eq filterKeyword.id }) { - it[filterId] = filterKeyword.filterId - it[keyword] = filterKeyword.keyword - it[mode] = filterKeyword.mode.name - } - } - filterKeyword - } - - override suspend fun saveAll(filterKeywordList: List): Unit = query { - FilterKeywords.batchInsert(filterKeywordList, ignore = true) { - this[FilterKeywords.id] = it.id - this[FilterKeywords.filterId] = it.filterId - this[FilterKeywords.keyword] = it.keyword - this[FilterKeywords.mode] = it.mode.name - } - } - - override suspend fun findById(id: Long): FilterKeyword? = query { - return@query FilterKeywords.selectAll().where { FilterKeywords.id eq id }.singleOrNull()?.toFilterKeyword() - } - - override suspend fun deleteById(id: Long): Unit = query { - FilterKeywords.deleteWhere { FilterKeywords.id eq id } - } - - override suspend fun deleteByFilterId(filterId: Long): Unit = query { - FilterKeywords.deleteWhere { FilterKeywords.filterId eq filterId } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedFilterKeywordRepository::class.java) - } -} - -fun ResultRow.toFilterKeyword(): FilterKeyword { - return FilterKeyword( - this[FilterKeywords.id], - this[FilterKeywords.filterId], - this[FilterKeywords.keyword], - this[FilterKeywords.mode].let { FilterMode.valueOf(it) } - ) -} - -object FilterKeywords : Table("filter_keywords") { - val id = long("id") - val filterId = long("filter_id").references(Filters.id) - val keyword = varchar("keyword", 1000) - val mode = varchar("mode", 100) - - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt index 69aec93b..9db15afb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt @@ -16,11 +16,11 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository -import dev.usbharu.hideout.application.service.id.IdGenerateService 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.FilterId +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId import dev.usbharu.hideout.core.domain.model.filter.FilterRepository -import dev.usbharu.hideout.core.domain.model.filter.FilterType +import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.slf4j.Logger @@ -28,56 +28,48 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository @Repository -class ExposedFilterRepository(private val idGenerateService: IdGenerateService) : FilterRepository, +class ExposedFilterRepository(private val filterQueryMapper: QueryMapper) : FilterRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger - override suspend fun generateId(): Long = idGenerateService.generateId() - override suspend fun save(filter: Filter): Filter = query { - val empty = Filters.selectAll().where { - Filters.id eq filter.id - }.forUpdate().empty() - if (empty) { - Filters.insert { - it[id] = filter.id - it[userId] = filter.userId - it[name] = filter.name - it[context] = filter.context.joinToString(",") { filterType -> filterType.name } - it[filterAction] = filter.filterAction.name - } - } else { - Filters.update({ Filters.id eq filter.id }) { - it[userId] = filter.userId - it[name] = filter.name - it[context] = filter.context.joinToString(",") { filterType -> filterType.name } - it[filterAction] = filter.filterAction.name - } + Filters.upsert { + it[id] = filter.id.id + it[userId] = filter.userDetailId.id + it[name] = filter.name.name + it[context] = filter.filterContext.joinToString(",") { it.name } + it[filterAction] = filter.filterAction.name + } + FilterKeywords.deleteWhere { + filterId eq filter.id.id + } + FilterKeywords.batchUpsert(filter.filterKeywords) { + this[FilterKeywords.id] = it.id.id + this[FilterKeywords.filterId] = filter.id.id + this[FilterKeywords.keyword] = it.keyword.keyword + this[FilterKeywords.mode] = it.mode.name } filter } - override suspend fun findById(id: Long): Filter? = query { - return@query Filters.selectAll().where { Filters.id eq id }.singleOrNull()?.toFilter() + override suspend fun delete(filter: Filter): Unit = query { + FilterKeywords.deleteWhere { filterId eq filter.id.id } + Filters.deleteWhere { id eq filter.id.id } } - override suspend fun findByUserIdAndId(userId: Long, id: Long): Filter? = query { - return@query Filters.selectAll().where { Filters.userId eq userId and (Filters.id eq id) }.singleOrNull() - ?.toFilter() + override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? { + val filterId = FilterKeywords + .selectAll() + .where { FilterKeywords.id eq filterKeywordId.id } + .firstOrNull()?.get(FilterKeywords.filterId) ?: return null + val where = Filters.selectAll().where { Filters.id eq filterId } + return filterQueryMapper.map(where).firstOrNull() } - override suspend fun findByUserIdAndType(userId: Long, types: List): List = query { - return@query Filters.selectAll().where { Filters.userId eq userId }.map { it.toFilter() } - .filter { it.context.containsAll(types) } - } - - override suspend fun deleteById(id: Long): Unit = query { - Filters.deleteWhere { Filters.id eq id } - } - - override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { - Filters.deleteWhere { Filters.userId eq userId and (Filters.id eq id) } + override suspend fun findByFilterId(filterId: FilterId): Filter? { + val where = Filters.selectAll().where { Filters.id eq filterId.id } + return filterQueryMapper.map(where).firstOrNull() } companion object { @@ -85,20 +77,22 @@ class ExposedFilterRepository(private val idGenerateService: IdGenerateService) } } -fun ResultRow.toFilter(): Filter = Filter( - this[Filters.id], - this[Filters.userId], - this[Filters.name], - this[Filters.context].split(",").filterNot(String::isEmpty).map { FilterType.valueOf(it) }, - this[Filters.filterAction].let { FilterAction.valueOf(it) } -) - -object Filters : Table() { +object Filters : Table("filters") { val id = long("id") - val userId = long("user_id").references(Actors.id) + val userId = long("user_id").references(UserDetails.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val name = varchar("name", 255) val context = varchar("context", 500) val filterAction = varchar("action", 255) override val primaryKey: PrimaryKey = PrimaryKey(id) } + +object FilterKeywords : Table("filter_keywords") { + val id = long("id") + val filterId = + long("filter_id").references(Filters.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) + val keyword = varchar("keyword", 1000) + val mode = varchar("mode", 100) + + override val primaryKey: PrimaryKey = PrimaryKey(id) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt new file mode 100644 index 00000000..43d63e27 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt @@ -0,0 +1,220 @@ +/* + * 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.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.post.* +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository +import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.actorId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.apId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.content +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.hide +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.moveTo +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.repostId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.sensitive +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.text +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.url +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.visibility +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList +import org.jetbrains.exposed.sql.javatime.timestamp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedPostRepository( + private val postQueryMapper: QueryMapper, + override val domainEventPublisher: DomainEventPublisher, +) : + PostRepository, + AbstractRepository(), + DomainEventPublishableRepository { + override val logger: Logger = Companion.logger + + override suspend fun save(post: Post): Post { + query { + Posts.upsert { + it[id] = post.id.id + it[actorId] = post.actorId.id + it[overview] = post.overview?.overview + it[content] = post.content.content + it[text] = post.content.text + it[createdAt] = post.createdAt + it[visibility] = post.visibility.name + it[url] = post.url.toString() + it[repostId] = post.repostId?.id + it[replyId] = post.replyId?.id + it[sensitive] = post.sensitive + it[apId] = post.apId.toString() + it[deleted] = post.deleted + it[hide] = post.hide + it[moveTo] = post.moveTo?.id + } + PostsMedia.deleteWhere { + postId eq post.id.id + } + PostsEmojis.deleteWhere { + postId eq post.id.id + } + PostsVisibleActors.deleteWhere { + postId eq post.id.id + } + PostsMedia.batchInsert(post.mediaIds) { + this[PostsMedia.postId] = post.id.id + this[PostsMedia.mediaId] = it.id + } + PostsEmojis.batchInsert(post.emojiIds) { + this[PostsEmojis.postId] = post.id.id + this[PostsEmojis.emojiId] = it.emojiId + } + PostsVisibleActors.batchInsert(post.visibleActors) { + this[PostsVisibleActors.postId] = post.id.id + this[PostsVisibleActors.actorId] = it.id + } + } + update(post) + return post + } + + override suspend fun saveAll(posts: List): List { + query { + Posts.batchUpsert(posts, id) { + this[id] = it.id.id + this[actorId] = it.actorId.id + this[overview] = it.overview?.overview + this[content] = it.content.content + this[text] = it.content.text + this[createdAt] = it.createdAt + this[visibility] = it.visibility.name + this[url] = it.url.toString() + this[repostId] = it.repostId?.id + this[replyId] = it.replyId?.id + this[sensitive] = it.sensitive + this[apId] = it.apId.toString() + this[deleted] = it.deleted + this[hide] = it.hide + this[moveTo] = it.moveTo?.id + } + val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id.id to it.id } } + val postsIds = posts.map { it.id.id } + PostsMedia.deleteWhere { + postId inList postsIds + } + PostsMedia.batchInsert(mediaIds) { + this[PostsMedia.postId] = it.first + this[PostsMedia.mediaId] = it.second + } + val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id.id to it.emojiId } } + PostsEmojis.deleteWhere { + postId inList postsIds + } + PostsEmojis.batchInsert(emojiIds) { + this[PostsEmojis.postId] = it.first + this[PostsEmojis.emojiId] = it.second + } + val visibleActors = posts.flatMap { post -> post.visibleActors.map { post.id.id to it.id } } + PostsVisibleActors.deleteWhere { + postId inList postsIds + } + PostsVisibleActors.batchInsert(visibleActors) { + this[PostsVisibleActors.postId] = it.first + this[PostsVisibleActors.actorId] = it.second + } + } + posts.forEach { + update(it) + } + return posts + } + + override suspend fun findById(id: PostId): Post? = query { + Posts + .selectAll() + .where { + Posts.id eq id.id + } + .let(postQueryMapper::map) + .first() + } + + override suspend fun findByActorId(id: ActorId): List = query { + Posts + .selectAll() + .where { + actorId eq id.id + } + .let(postQueryMapper::map) + } + + override suspend fun delete(post: Post) { + query { + Posts.deleteWhere { + id eq post.id.id + } + } + update(post) + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedPostRepository::class.java) + } +} + +object Posts : Table("posts") { + val id = long("id") + val actorId = long("actor_id").references(Actors.id) + val overview = varchar("overview", PostOverview.LENGTH).nullable() + val content = varchar("content", PostContent.CONTENT_LENGTH) + val text = varchar("text", PostContent.TEXT_LENGTH) + val createdAt = timestamp("created_at") + val visibility = varchar("visibility", 100) + val url = varchar("url", 1000) + val repostId = long("repost_id").references(id).nullable() + val replyId = long("reply_id").references(id).nullable() + val sensitive = bool("sensitive") + val apId = varchar("ap_id", 1000) + val deleted = bool("deleted") + val hide = bool("hide") + val moveTo = long("move_to").references(id).nullable() + override val primaryKey: PrimaryKey = PrimaryKey(id) +} + +object PostsMedia : Table("posts_media") { + val postId = long("post_id").references(id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + override val primaryKey = PrimaryKey(postId, mediaId) +} + +object PostsEmojis : Table("posts_emojis") { + val postId = long("post_id").references(id) + val emojiId = long("emoji_id").references(CustomEmojis.id) + override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) +} + +object PostsVisibleActors : Table("posts_visible_actors") { + val postId = long("post_id").references(id) + val actorId = long("actor_id").references(Actors.id) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt new file mode 100644 index 00000000..c658754c --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt @@ -0,0 +1,94 @@ +/* + * 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.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedRelationshipRepository(override val domainEventPublisher: DomainEventPublisher) : + RelationshipRepository, + AbstractRepository(), + DomainEventPublishableRepository { + override val logger: Logger + get() = Companion.logger + + override suspend fun save(relationship: Relationship): Relationship { + query { + Relationships.upsert { + it[actorId] = relationship.actorId.id + it[targetActorId] = relationship.targetActorId.id + it[following] = relationship.following + it[blocking] = relationship.blocking + it[muting] = relationship.muting + it[followRequesting] = relationship.followRequesting + it[mutingFollowRequest] = relationship.mutingFollowRequest + } + } + update(relationship) + return relationship + } + + override suspend fun delete(relationship: Relationship) { + query { + Relationships.deleteWhere { + actorId eq relationship.actorId.id and (targetActorId eq relationship.targetActorId.id) + } + } + update(relationship) + } + + override suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? = query { + Relationships.selectAll().where { + Relationships.actorId eq actorId.id and (Relationships.targetActorId eq targetId.id) + }.singleOrNull()?.toRelationships() + } + + companion object { + private val logger = LoggerFactory.getLogger(ExposedRelationshipRepository::class.java) + } +} + +fun ResultRow.toRelationships(): Relationship = Relationship( + actorId = ActorId(this[Relationships.actorId]), + targetActorId = ActorId(this[Relationships.targetActorId]), + following = this[Relationships.following], + blocking = this[Relationships.blocking], + muting = this[Relationships.muting], + followRequesting = this[Relationships.followRequesting], + mutingFollowRequest = this[Relationships.mutingFollowRequest] +) + +object Relationships : Table("relationships") { + val actorId = long("actor_id").references(Actors.id) + val targetActorId = long("target_actor_id").references(Actors.id) + val following = bool("following") + val blocking = bool("blocking") + val muting = bool("muting") + val followRequesting = bool("follow_requesting") + val mutingFollowRequest = bool("muting_follow_request") + + override val primaryKey: PrimaryKey = PrimaryKey(actorId, targetActorId) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt deleted file mode 100644 index eb17a1ef..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import org.jetbrains.exposed.sql.* -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Repository - -@Repository -@Qualifier("jdbc") -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) -class ExposedTimelineRepository(private val idGenerateService: IdGenerateService) : TimelineRepository, - AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(timeline: Timeline): Timeline = query { - if (Timelines.selectAll().where { Timelines.id eq timeline.id }.forUpdate().singleOrNull() == null) { - Timelines.insert { - it[id] = timeline.id - it[userId] = timeline.userId - it[timelineId] = timeline.timelineId - it[postId] = timeline.postId - it[postActorId] = timeline.postActorId - it[createdAt] = timeline.createdAt - it[replyId] = timeline.replyId - it[repostId] = timeline.repostId - it[visibility] = timeline.visibility.ordinal - it[sensitive] = timeline.sensitive - it[isLocal] = timeline.isLocal - it[isPureRepost] = timeline.isPureRepost - it[mediaIds] = timeline.mediaIds.joinToString(",") - it[emojiIds] = timeline.emojiIds.joinToString(",") - } - } else { - Timelines.update({ Timelines.id eq timeline.id }) { - it[userId] = timeline.userId - it[timelineId] = timeline.timelineId - it[postId] = timeline.postId - it[postActorId] = timeline.postActorId - it[createdAt] = timeline.createdAt - it[replyId] = timeline.replyId - it[repostId] = timeline.repostId - it[visibility] = timeline.visibility.ordinal - it[sensitive] = timeline.sensitive - it[isLocal] = timeline.isLocal - it[isPureRepost] = timeline.isPureRepost - it[mediaIds] = timeline.mediaIds.joinToString(",") - it[emojiIds] = timeline.emojiIds.joinToString(",") - } - } - return@query timeline - } - - override suspend fun saveAll(timelines: List): List = query { - Timelines.batchInsert(timelines, true, false) { - this[Timelines.id] = it.id - this[Timelines.userId] = it.userId - this[Timelines.timelineId] = it.timelineId - this[Timelines.postId] = it.postId - this[Timelines.postActorId] = it.postActorId - this[Timelines.createdAt] = it.createdAt - this[Timelines.replyId] = it.replyId - this[Timelines.repostId] = it.repostId - this[Timelines.visibility] = it.visibility.ordinal - this[Timelines.sensitive] = it.sensitive - this[Timelines.isLocal] = it.isLocal - this[Timelines.isPureRepost] = it.isPureRepost - this[Timelines.mediaIds] = it.mediaIds.joinToString(",") - this[Timelines.emojiIds] = it.emojiIds.joinToString(",") - } - return@query timelines - } - - override suspend fun findByUserId(id: Long): List = query { - return@query Timelines.selectAll().where { Timelines.userId eq id }.map { it.toTimeline() } - } - - override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List = query { - return@query Timelines.selectAll().where { Timelines.userId eq userId and (Timelines.timelineId eq timelineId) } - .map { it.toTimeline() } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedTimelineRepository::class.java) - } -} - -fun ResultRow.toTimeline(): Timeline { - return Timeline( - id = this[Timelines.id], - userId = this[Timelines.userId], - timelineId = this[Timelines.timelineId], - postId = this[Timelines.postId], - postActorId = this[Timelines.postActorId], - createdAt = this[Timelines.createdAt], - replyId = this[Timelines.replyId], - repostId = this[Timelines.repostId], - visibility = Visibility.values().first { it.ordinal == this[Timelines.visibility] }, - sensitive = this[Timelines.sensitive], - isLocal = this[Timelines.isLocal], - isPureRepost = this[Timelines.isPureRepost], - mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() }, - emojiIds = this[Timelines.emojiIds].split(",").mapNotNull { it.toLongOrNull() } - ) -} - -object Timelines : Table("timelines") { - val id = long("id") - val userId = long("user_id") - val timelineId = long("timeline_id") - val postId = long("post_id") - val postActorId = long("post_actor_id") - val createdAt = long("created_at") - val replyId = long("reply_id").nullable() - val repostId = long("repost_id").nullable() - val visibility = integer("visibility") - val sensitive = bool("sensitive") - val isLocal = bool("is_local") - val isPureRepost = bool("is_pure_repost") - val mediaIds = varchar("media_ids", 255) - val emojiIds = varchar("emoji_ids", 255) - - override val primaryKey: PrimaryKey = PrimaryKey(id) - - init { - uniqueIndex(userId, timelineId, postId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt index 2084b4fa..76844bed 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt @@ -16,69 +16,67 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository +import dev.usbharu.hideout.core.domain.model.instance.* import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.javatime.timestamp import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository +import java.net.URI import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity @Repository -class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : InstanceRepository, +class InstanceRepositoryImpl : InstanceRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger - override suspend fun generateId(): Long = idGenerateService.generateId() - override suspend fun save(instance: InstanceEntity): InstanceEntity = query { - if (Instance.selectAll().where { Instance.id.eq(instance.id) }.forUpdate().empty()) { + if (Instance.selectAll().where { Instance.id.eq(instance.id.instanceId) }.forUpdate().empty()) { Instance.insert { - it[id] = instance.id - it[name] = instance.name - it[description] = instance.description - it[url] = instance.url - it[iconUrl] = instance.iconUrl - it[sharedInbox] = instance.sharedInbox - it[software] = instance.software - it[version] = instance.version + it[id] = instance.id.instanceId + it[name] = instance.name.name + it[description] = instance.description.description + it[url] = instance.url.toString() + it[iconUrl] = instance.iconUrl.toString() + it[sharedInbox] = instance.sharedInbox?.toString() + it[software] = instance.software.software + it[version] = instance.version.version it[isBlocked] = instance.isBlocked it[isMuted] = instance.isMuted - it[moderationNote] = instance.moderationNote + it[moderationNote] = instance.moderationNote.note it[createdAt] = instance.createdAt } } else { - Instance.update({ Instance.id eq instance.id }) { - it[name] = instance.name - it[description] = instance.description - it[url] = instance.url - it[iconUrl] = instance.iconUrl - it[sharedInbox] = instance.sharedInbox - it[software] = instance.software - it[version] = instance.version + Instance.update({ Instance.id eq instance.id.instanceId }) { + it[name] = instance.name.name + it[description] = instance.description.description + it[url] = instance.url.toString() + it[iconUrl] = instance.iconUrl.toString() + it[sharedInbox] = instance.sharedInbox?.toString() + it[software] = instance.software.software + it[version] = instance.version.version it[isBlocked] = instance.isBlocked it[isMuted] = instance.isMuted - it[moderationNote] = instance.moderationNote + it[moderationNote] = instance.moderationNote.note it[createdAt] = instance.createdAt } } return@query instance } - override suspend fun findById(id: Long): InstanceEntity? = query { - return@query Instance.selectAll().where { Instance.id eq id } + override suspend fun findById(id: InstanceId): InstanceEntity? = query { + return@query Instance.selectAll().where { Instance.id eq id.instanceId } .singleOrNull()?.toInstance() } override suspend fun delete(instance: InstanceEntity): Unit = query { - Instance.deleteWhere { id eq instance.id } + Instance.deleteWhere { id eq instance.id.instanceId } } - override suspend fun findByUrl(url: String): dev.usbharu.hideout.core.domain.model.instance.Instance? = query { - return@query Instance.selectAll().where { Instance.url eq url }.singleOrNull()?.toInstance() + override suspend fun findByUrl(url: URI): dev.usbharu.hideout.core.domain.model.instance.Instance? = query { + return@query Instance.selectAll().where { Instance.url eq url.toString() }.singleOrNull()?.toInstance() } companion object { @@ -88,17 +86,17 @@ class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : fun ResultRow.toInstance(): InstanceEntity { return InstanceEntity( - id = this[Instance.id], - name = this[Instance.name], - description = this[Instance.description], - url = this[Instance.url], - iconUrl = this[Instance.iconUrl], - sharedInbox = this[Instance.sharedInbox], - software = this[Instance.software], - version = this[Instance.version], + id = InstanceId(this[Instance.id]), + name = InstanceName(this[Instance.name]), + description = InstanceDescription(this[Instance.description]), + url = URI.create(this[Instance.url]), + iconUrl = URI.create(this[Instance.iconUrl]), + sharedInbox = this[Instance.sharedInbox]?.let { URI.create(it) }, + software = InstanceSoftware(this[Instance.software]), + version = InstanceVersion(this[Instance.version]), isBlocked = this[Instance.isBlocked], isMuted = this[Instance.isMuted], - moderationNote = this[Instance.moderationNote], + moderationNote = InstanceModerationNote(this[Instance.moderationNote]), createdAt = this[Instance.createdAt] ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt index 19e5cf8e..b9ab43de 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt @@ -16,106 +16,82 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.media.MediaRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Media.mimeType -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MimeType +import dev.usbharu.hideout.core.domain.model.media.* import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository +import java.net.URI import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia @Repository -class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository, AbstractRepository() { +class MediaRepositoryImpl : MediaRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger - override suspend fun generateId(): Long = idGenerateService.generateId() + override suspend fun findById(id: MediaId): dev.usbharu.hideout.core.domain.model.media.Media? { + return query { + return@query Media + .selectAll().where { Media.id eq id.id } + .singleOrNull() + ?.toMedia() + } + } + + override suspend fun delete(media: dev.usbharu.hideout.core.domain.model.media.Media): Unit = query { + Media.deleteWhere { + id eq id + } + } override suspend fun save(media: EntityMedia): EntityMedia = query { - if (Media.selectAll().where { Media.id eq media.id }.forUpdate().singleOrNull() != null + if (Media.selectAll().where { Media.id eq media.id.id }.forUpdate().singleOrNull() != null ) { - Media.update({ Media.id eq media.id }) { - it[name] = media.name - it[url] = media.url - it[remoteUrl] = media.remoteUrl - it[thumbnailUrl] = media.thumbnailUrl - it[type] = media.type.ordinal - it[blurhash] = media.blurHash + Media.update({ Media.id eq media.id.id }) { + it[name] = media.name.name + it[url] = media.url.toString() + it[remoteUrl] = media.remoteUrl?.toString() + it[thumbnailUrl] = media.thumbnailUrl?.toString() + it[type] = media.type.name + it[blurhash] = media.blurHash?.hash it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype - it[description] = media.description + it[description] = media.description?.description } } else { Media.insert { - it[id] = media.id - it[name] = media.name - it[url] = media.url - it[remoteUrl] = media.remoteUrl - it[thumbnailUrl] = media.thumbnailUrl - it[type] = media.type.ordinal - it[blurhash] = media.blurHash + it[id] = media.id.id + it[name] = media.name.name + it[url] = media.url.toString() + it[remoteUrl] = media.remoteUrl?.toString() + it[thumbnailUrl] = media.thumbnailUrl?.toString() + it[type] = media.type.name + it[blurhash] = media.blurHash?.hash it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype - it[description] = media.description + it[description] = media.description?.description } } return@query media } - override suspend fun findById(id: Long): EntityMedia? = query { - return@query Media - .selectAll().where { Media.id eq id } - .singleOrNull() - ?.toMedia() - } - - override suspend fun delete(id: Long): Unit = query { - Media.deleteWhere { - Media.id eq id - } - } - - override suspend fun findByRemoteUrl(remoteUrl: String): dev.usbharu.hideout.core.domain.model.media.Media? = - query { - return@query Media.selectAll().where { Media.remoteUrl eq remoteUrl }.singleOrNull()?.toMedia() - } - companion object { private val logger = LoggerFactory.getLogger(MediaRepositoryImpl::class.java) } } fun ResultRow.toMedia(): EntityMedia { - val fileType = FileType.values().first { it.ordinal == this[Media.type] } + val fileType = FileType.valueOf(this[Media.type]) val mimeType = this[Media.mimeType] return EntityMedia( - id = this[Media.id], - name = this[Media.name], - url = this[Media.url], - remoteUrl = this[Media.remoteUrl], - thumbnailUrl = this[Media.thumbnailUrl], + id = MediaId(this[Media.id]), + name = MediaName(this[Media.name]), + url = URI.create(this[Media.url]), + remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) }, + thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) }, type = fileType, - blurHash = this[Media.blurhash], + blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) }, mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), - description = this[Media.description] - ) -} - -fun ResultRow.toMediaOrNull(): EntityMedia? { - val fileType = FileType.values().first { it.ordinal == (this.getOrNull(Media.type) ?: return null) } - val mimeType = this.getOrNull(Media.mimeType) ?: return null - return EntityMedia( - id = this.getOrNull(Media.id) ?: return null, - name = this.getOrNull(Media.name) ?: return null, - url = this.getOrNull(Media.url) ?: return null, - remoteUrl = this[Media.remoteUrl], - thumbnailUrl = this[Media.thumbnailUrl], - type = FileType.values().first { it.ordinal == this.getOrNull(Media.type) }, - blurHash = this[Media.blurhash], - mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), - description = this[Media.description] + description = this[Media.description]?.let { MediaDescription(it) } ) } @@ -125,7 +101,7 @@ object Media : Table("media") { val url = varchar("url", 255).uniqueIndex() val remoteUrl = varchar("remote_url", 255).uniqueIndex().nullable() val thumbnailUrl = varchar("thumbnail_url", 255).uniqueIndex().nullable() - val type = integer("type") + val type = varchar("type", 100) val blurhash = varchar("blurhash", 255).nullable() val mimeType = varchar("mime_type", 255) val description = varchar("description", 4000).nullable() diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MetaRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MetaRepositoryImpl.kt deleted file mode 100644 index c9d76578..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MetaRepositoryImpl.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.MetaRepository -import org.jetbrains.exposed.sql.* -import org.springframework.stereotype.Repository -import java.util.* - -@Repository -class MetaRepositoryImpl : MetaRepository { - - override suspend fun save(meta: dev.usbharu.hideout.core.domain.model.meta.Meta) { - if (Meta.selectAll().where { Meta.id eq 1 }.empty()) { - Meta.insert { - it[id] = 1 - it[version] = meta.version - it[kid] = UUID.randomUUID().toString() - it[jwtPrivateKey] = meta.jwt.privateKey - it[jwtPublicKey] = meta.jwt.publicKey - } - } else { - Meta.update({ Meta.id eq 1 }) { - it[version] = meta.version - it[kid] = UUID.randomUUID().toString() - it[jwtPrivateKey] = meta.jwt.privateKey - it[jwtPublicKey] = meta.jwt.publicKey - } - } - } - - override suspend fun get(): dev.usbharu.hideout.core.domain.model.meta.Meta? { - return Meta.selectAll().where { Meta.id eq 1 }.singleOrNull()?.let { - dev.usbharu.hideout.core.domain.model.meta.Meta( - it[Meta.version], - Jwt(UUID.fromString(it[Meta.kid]), it[Meta.jwtPrivateKey], it[Meta.jwtPublicKey]) - ) - } - } -} - -object Meta : Table("meta_info") { - val id: Column = long("id") - val version: Column = varchar("version", 1000) - val kid: Column = varchar("kid", 1000) - val jwtPrivateKey: Column = varchar("jwt_private_key", 100000) - val jwtPublicKey: Column = varchar("jwt_public_key", 100000) - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt deleted file mode 100644 index 96180244..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.actorId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.apId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.content -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.repostId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.sensitive -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.text -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.url -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.visibility -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class PostRepositoryImpl( - private val idGenerateService: IdGenerateService, - private val postQueryMapper: QueryMapper, -) : PostRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(post: Post): Post = query { - val singleOrNull = Posts.selectAll().where { id eq post.id }.forUpdate().singleOrNull() - if (singleOrNull == null) { - Posts.insert { - it[id] = post.id - it[actorId] = post.actorId - it[overview] = post.overview - it[content] = post.content - it[text] = post.text - it[createdAt] = post.createdAt - it[visibility] = post.visibility.ordinal - it[url] = post.url - it[repostId] = post.repostId - it[replyId] = post.replyId - it[sensitive] = post.sensitive - it[apId] = post.apId - it[deleted] = post.deleted - } - PostsMedia.batchInsert(post.mediaIds) { - this[PostsMedia.postId] = post.id - this[PostsMedia.mediaId] = it - } - PostsEmojis.batchInsert(post.emojiIds) { - this[PostsEmojis.postId] = post.id - this[PostsEmojis.emojiId] = it - } - } else { - PostsMedia.deleteWhere { - postId eq post.id - } - PostsEmojis.deleteWhere { - postId eq post.id - } - PostsMedia.batchInsert(post.mediaIds) { - this[PostsMedia.postId] = post.id - this[PostsMedia.mediaId] = it - } - PostsEmojis.batchInsert(post.emojiIds) { - this[PostsEmojis.postId] = post.id - this[PostsEmojis.emojiId] = it - } - Posts.update({ id eq post.id }) { - it[actorId] = post.actorId - it[overview] = post.overview - it[content] = post.content - it[text] = post.text - it[createdAt] = post.createdAt - it[visibility] = post.visibility.ordinal - it[url] = post.url - it[repostId] = post.repostId - it[replyId] = post.replyId - it[sensitive] = post.sensitive - it[apId] = post.apId - it[deleted] = post.deleted - } - } - return@query post - } - - override suspend fun saveAll(posts: List) { - Posts.batchUpsert( - posts, - id, - ) { - this[id] = it.id - this[actorId] = it.actorId - this[overview] = it.overview - this[content] = it.content - this[text] = it.text - this[createdAt] = it.createdAt - this[visibility] = it.visibility.ordinal - this[url] = it.url - this[repostId] = it.repostId - this[replyId] = it.replyId - this[sensitive] = it.sensitive - this[apId] = it.apId - this[deleted] = it.deleted - } - val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id to it } } - PostsMedia.batchUpsert( - mediaIds, - PostsMedia.postId - ) { - this[PostsMedia.postId] = it.first - this[PostsMedia.mediaId] = it.second - } - - val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id to it } } - PostsEmojis.batchUpsert(emojiIds, PostsEmojis.postId) { - this[PostsEmojis.postId] = it.first - this[PostsEmojis.emojiId] = it.second - } - } - - override suspend fun findById(id: Long): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.id eq id } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun findByUrl(url: String): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.url eq url } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun findByApId(apId: String): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.apId eq apId } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun existByApIdWithLock(apId: String): Boolean = query { - return@query Posts.selectAll().where { Posts.apId eq apId }.forUpdate().empty().not() - } - - override suspend fun findByActorId(actorId: Long): List = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.actorId eq actorId }.let(postQueryMapper::map) - } - - override suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List { - TODO("Not yet implemented") - } - - override suspend fun countByActorId(actorId: Long): Int = query { - return@query Posts - .selectAll() - .where { Posts.actorId eq actorId } - .count() - .toInt() - } - - override suspend fun delete(id: Long): Unit = query { - Posts.deleteWhere { Posts.id eq id } - } - - companion object { - private val logger = LoggerFactory.getLogger(PostRepositoryImpl::class.java) - } -} - -object Posts : Table() { - val id: Column = long("id") - val actorId: Column = long("actor_id").references(Actors.id) - val overview: Column = varchar("overview", 100).nullable() - val content = varchar("content", 5000) - val text: Column = varchar("text", 3000) - val createdAt: Column = long("created_at") - val visibility: Column = integer("visibility").default(0) - val url: Column = varchar("url", 500) - val repostId: Column = long("repost_id").references(id).nullable() - val replyId: Column = long("reply_id").references(id).nullable() - val sensitive: Column = bool("sensitive").default(false) - val apId: Column = varchar("ap_id", 100).uniqueIndex() - val deleted = bool("deleted").default(false) - override val primaryKey: PrimaryKey = PrimaryKey(id) -} - -object PostsMedia : Table("posts_media") { - val postId = long("post_id").references(id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) - val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) - override val primaryKey = PrimaryKey(postId, mediaId) -} - -object PostsEmojis : Table("posts_emojis") { - val postId = long("post_id").references(id) - val emojiId = long("emoji_id").references(CustomEmojis.id) - override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt deleted file mode 100644 index e9be4342..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.emoji.Emoji -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.domain.model.reaction.Reaction -import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ReactionRepositoryImpl( - private val idGenerateService: IdGenerateService -) : ReactionRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(reaction: Reaction): Reaction = query { - if (Reactions.selectAll().where { Reactions.id eq reaction.id }.forUpdate().empty()) { - Reactions.insert { - it[id] = reaction.id - if (reaction.emoji is CustomEmoji) { - it[customEmojiId] = reaction.emoji.id - it[unicodeEmoji] = null - } else { - it[customEmojiId] = null - it[unicodeEmoji] = reaction.emoji.name - } - it[postId] = reaction.postId - it[actorId] = reaction.actorId - } - } else { - Reactions.update({ Reactions.id eq reaction.id }) { - if (reaction.emoji is CustomEmoji) { - it[customEmojiId] = reaction.emoji.id - it[unicodeEmoji] = null - } else { - it[customEmojiId] = null - it[unicodeEmoji] = reaction.emoji.name - } - it[postId] = reaction.postId - it[actorId] = reaction.actorId - } - } - return@query reaction - } - - override suspend fun delete(reaction: Reaction): Reaction = query { - if (reaction.emoji is CustomEmoji) { - Reactions.deleteWhere { - id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId)) - .and(customEmojiId.eq(reaction.emoji.id)) - } - } else { - Reactions.deleteWhere { - id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId)) - .and(unicodeEmoji.eq(reaction.emoji.name)) - } - } - return@query reaction - } - - override suspend fun deleteByPostId(postId: Long): Int = query { - return@query Reactions.deleteWhere { - Reactions.postId eq postId - } - } - - override suspend fun deleteByActorId(actorId: Long): Int = query { - return@query Reactions.deleteWhere { - Reactions.actorId eq actorId - } - } - - override suspend fun deleteByPostIdAndActorId(postId: Long, actorId: Long): Unit = query { - Reactions.deleteWhere { - Reactions.postId eq postId and (Reactions.actorId eq actorId) - } - } - - override suspend fun deleteByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Unit = query { - if (emoji is CustomEmoji) { - Reactions.deleteWhere { - Reactions.postId.eq(postId) - .and(Reactions.actorId.eq(actorId)) - .and(customEmojiId.eq(emoji.id)) - } - } else { - Reactions.deleteWhere { - Reactions.postId.eq(postId) - .and(Reactions.actorId.eq(actorId)) - .and(unicodeEmoji.eq(emoji.name)) - } - } - } - - override suspend fun findById(id: Long): Reaction? = query { - return@query Reactions.leftJoin(CustomEmojis).selectAll().where { Reactions.id eq id }.singleOrNull() - ?.toReaction() - } - - override suspend fun findByPostId(postId: Long): List = query { - return@query Reactions.leftJoin(CustomEmojis).selectAll().where { Reactions.postId eq postId } - .map { it.toReaction() } - } - - override suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? = - query { - return@query Reactions.leftJoin(CustomEmojis).selectAll().where { - Reactions.postId eq postId and (Reactions.actorId eq actorId).and( - Reactions.customEmojiId.eq( - emojiId - ) - ) - }.singleOrNull()?.toReaction() - } - - override suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean = - query { - return@query Reactions.selectAll().where { - Reactions.postId - .eq(postId) - .and(Reactions.actorId.eq(actorId)) - .and(Reactions.customEmojiId.eq(emojiId)) - }.empty().not() - } - - override suspend fun existByPostIdAndActorIdAndUnicodeEmoji( - postId: Long, - actorId: Long, - unicodeEmoji: String - ): Boolean = query { - return@query Reactions.selectAll().where { - Reactions.postId - .eq(postId) - .and(Reactions.actorId.eq(actorId)) - .and(Reactions.unicodeEmoji.eq(unicodeEmoji)) - }.empty().not() - } - - override suspend fun existByPostIdAndActorIdAndEmoji(postId: Long, actorId: Long, emoji: Emoji): Boolean = query { - val query = Reactions.selectAll().where { - Reactions.postId - .eq(postId) - .and(Reactions.actorId.eq(actorId)) - } - - if (emoji is UnicodeEmoji) { - query.andWhere { Reactions.unicodeEmoji eq emoji.name } - } else { - emoji as CustomEmoji - query.andWhere { Reactions.customEmojiId eq emoji.id } - } - - return@query query.empty().not() - } - - override suspend fun existByPostIdAndActor(postId: Long, actorId: Long): Boolean = query { - Reactions.selectAll().where { Reactions.postId.eq(postId).and(Reactions.actorId.eq(actorId)) }.empty().not() - } - - override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List = query { - return@query Reactions.leftJoin(CustomEmojis) - .selectAll().where { Reactions.postId eq postId and (Reactions.actorId eq actorId) } - .map { it.toReaction() } - } - - companion object { - private val logger = LoggerFactory.getLogger(ReactionRepositoryImpl::class.java) - } -} - -fun ResultRow.toReaction(): Reaction { - val emoji = if (this[Reactions.customEmojiId] != null) { - CustomEmoji( - id = this[Reactions.customEmojiId]!!, - name = this[CustomEmojis.name], - domain = this[CustomEmojis.domain], - instanceId = this[CustomEmojis.instanceId], - url = this[CustomEmojis.url], - category = this[CustomEmojis.category], - createdAt = this[CustomEmojis.createdAt] - ) - } else if (this[Reactions.unicodeEmoji] != null) { - UnicodeEmoji(this[Reactions.unicodeEmoji]!!) - } else { - throw IllegalStateException("customEmojiId and unicodeEmoji is null.") - } - - return Reaction( - this[Reactions.id].value, - emoji, - this[Reactions.postId], - this[Reactions.actorId] - ) -} - -object Reactions : LongIdTable("reactions") { - val customEmojiId = long("custom_emoji_id").references(CustomEmojis.id).nullable() - val unicodeEmoji = varchar("unicode_emoji", 255).nullable() - val postId: Column = - long("post_id").references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) - val actorId: Column = - long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) - - init { - uniqueIndex(customEmojiId, postId, actorId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt index ef003ab7..03ff1695 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt @@ -16,14 +16,14 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository +import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository -import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.update +import org.jetbrains.exposed.sql.javatime.timestamp import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository @@ -35,24 +35,28 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() { override suspend fun save(userDetail: UserDetail): UserDetail = query { val singleOrNull = - UserDetails.selectAll().where { UserDetails.actorId eq userDetail.actorId }.forUpdate().singleOrNull() + UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull() if (singleOrNull == null) { UserDetails.insert { - it[actorId] = userDetail.actorId - it[password] = userDetail.password + it[id] = userDetail.id.id + it[actorId] = userDetail.actorId.id + it[password] = userDetail.password.password it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest + it[lastMigration] = userDetail.lastMigration } } else { - UserDetails.update({ UserDetails.actorId eq userDetail.actorId }) { - it[password] = userDetail.password + UserDetails.update({ UserDetails.id eq userDetail.id.id }) { + it[actorId] = userDetail.actorId.id + it[password] = userDetail.password.password it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest + it[lastMigration] = userDetail.lastMigration } } return@query userDetail } override suspend fun delete(userDetail: UserDetail): Unit = query { - UserDetails.deleteWhere { actorId eq userDetail.actorId } + UserDetails.deleteWhere { id eq userDetail.id.id } } override suspend fun findByActorId(actorId: Long): UserDetail? = query { @@ -60,10 +64,27 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() { .selectAll().where { UserDetails.actorId eq actorId } .singleOrNull() ?.let { - UserDetail( - it[UserDetails.actorId], - it[UserDetails.password], - it[UserDetails.autoAcceptFolloweeFollowRequest] + UserDetail.create( + UserDetailId(it[UserDetails.id]), + ActorId(it[UserDetails.actorId]), + UserDetailHashedPassword(it[UserDetails.password]), + it[UserDetails.autoAcceptFolloweeFollowRequest], + it[UserDetails.lastMigration] + ) + } + } + + override suspend fun findById(id: Long): UserDetail? = query { + UserDetails + .selectAll().where { UserDetails.id eq id } + .singleOrNull() + ?.let { + UserDetail.create( + UserDetailId(it[UserDetails.id]), + ActorId(it[UserDetails.actorId]), + UserDetailHashedPassword(it[UserDetails.password]), + it[UserDetails.autoAcceptFolloweeFollowRequest], + it[UserDetails.lastMigration] ) } } @@ -73,8 +94,11 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() { } } -object UserDetails : LongIdTable("user_details") { +object UserDetails : Table("user_details") { + val id = long("id") val actorId = long("actor_id").references(Actors.id) val password = varchar("password", 255) val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request") + val lastMigration = timestamp("last_migration").nullable() + override val primaryKey: PrimaryKey = PrimaryKey(id) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt new file mode 100644 index 00000000..0fb961c5 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt @@ -0,0 +1,71 @@ +/* + * 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.infrastructure.factory + +import dev.usbharu.hideout.core.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.model.actor.* +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.shared.Domain +import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService +import org.springframework.stereotype.Component +import java.net.URI +import java.time.Instant + +@Component +class ActorFactoryImpl( + private val idGenerateService: IdGenerateService, + private val applicationConfig: ApplicationConfig, +) { + suspend fun createLocal( + name: String, + keyPair: Pair, + instanceId: InstanceId, + ): Actor { + val actorName = ActorName(name) + val userUrl = "${applicationConfig.url}/users/${actorName.name}" + return Actor( + id = ActorId(idGenerateService.generateId()), + name = actorName, + domain = Domain(applicationConfig.url.host), + screenName = ActorScreenName(name), + description = ActorDescription(""), + inbox = URI.create("$userUrl/inbox"), + outbox = URI.create("$userUrl/outbox"), + url = URI.create(userUrl), + publicKey = keyPair.first, + privateKey = keyPair.second, + createdAt = Instant.now(), + keyId = ActorKeyId("$userUrl#main-key"), + followersEndpoint = URI.create("$userUrl/followers"), + followingEndpoint = URI.create("$userUrl/following"), + instance = instanceId, + locked = false, + followersCount = ActorRelationshipCount(0), + followingCount = ActorRelationshipCount(0), + postsCount = ActorPostsCount(0), + lastPostAt = null, + suspend = false, + emojiIds = emptySet(), + deleted = false, + roles = emptySet(), + banner = null, + icon = null + ) + } +} + +// todo なんか色々おかしいので直す diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverCreateTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostContentFactoryImpl.kt similarity index 55% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverCreateTask.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostContentFactoryImpl.kt index 2c645290..a59af46c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverCreateTask.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostContentFactoryImpl.kt @@ -14,21 +14,22 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.external.job +package dev.usbharu.hideout.core.infrastructure.factory -import dev.usbharu.hideout.activitypub.domain.model.Create -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition +import dev.usbharu.hideout.core.domain.model.post.PostContent +import dev.usbharu.hideout.core.domain.service.post.PostContentFormatter import org.springframework.stereotype.Component -data class DeliverCreateTask( - val create: Create, - val inbox: String, - val actor: String, -) : Task() - @Component -data object DeliverCreateTaskDef : TaskDefinition { - override val type: Class - get() = DeliverCreateTask::class.java +class PostContentFactoryImpl( + private val postContentFormatter: PostContentFormatter, +) { + suspend fun create(content: String): PostContent { + val format = postContentFormatter.format(content) + return PostContent( + format.content, + format.html, + emptyList() + ) + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostFactoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostFactoryImpl.kt new file mode 100644 index 00000000..4b1d4b29 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/PostFactoryImpl.kt @@ -0,0 +1,68 @@ +/* + * 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.infrastructure.factory + +import dev.usbharu.hideout.core.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.actor.ActorName +import dev.usbharu.hideout.core.domain.model.media.MediaId +import dev.usbharu.hideout.core.domain.model.post.Post +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.Visibility +import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService +import org.springframework.stereotype.Component +import java.net.URI +import java.time.Instant + +@Component +class PostFactoryImpl( + private val idGenerateService: IdGenerateService, + private val postContentFactoryImpl: PostContentFactoryImpl, + private val applicationConfig: ApplicationConfig, +) { + suspend fun createLocal( + actor: Actor, + actorName: ActorName, + overview: PostOverview?, + content: String, + visibility: Visibility, + repostId: PostId?, + replyId: PostId?, + sensitive: Boolean, + mediaIds: List, + ): Post { + val id = idGenerateService.generateId() + val url = URI.create(applicationConfig.url.toString() + "/users/" + actorName.name + "/posts/" + id) + return Post.create( + id = PostId(id), + actorId = actor.id, + overview = overview, + content = postContentFactoryImpl.create(content), + createdAt = Instant.now(), + visibility = visibility, + url = url, + repostId = repostId, + replyId = replyId, + sensitive = sensitive, + apId = url, + deleted = false, + mediaIds = mediaIds, + actor = actor, + ) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/httpsignature/HttpRequestMixIn.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/httpsignature/HttpRequestMixIn.kt deleted file mode 100644 index 5219c983..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/httpsignature/HttpRequestMixIn.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.infrastructure.httpsignature - -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import java.net.URL - -@JsonDeserialize(using = HttpRequestDeserializer::class) -@JsonSubTypes -@Suppress("UnnecessaryAbstractClass") -abstract class HttpRequestMixIn - -class HttpRequestDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): HttpRequest { - val readTree: JsonNode = p.codec.readTree(p) - - return HttpRequest( - URL(readTree["url"].textValue()), - HttpHeaders(emptyMap()), - HttpMethod.valueOf(readTree["method"].textValue()) - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/localfilesystem/LocalFileSystemMediaStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/localfilesystem/LocalFileSystemMediaStore.kt new file mode 100644 index 00000000..607ab397 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/localfilesystem/LocalFileSystemMediaStore.kt @@ -0,0 +1,46 @@ +package dev.usbharu.hideout.core.infrastructure.localfilesystem + +import dev.usbharu.hideout.core.config.ApplicationConfig +import dev.usbharu.hideout.core.config.LocalStorageConfig +import dev.usbharu.hideout.core.external.mediastore.MediaStore +import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component +import java.net.URI +import java.nio.file.Path +import kotlin.io.path.copyTo + +@Component +@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) +class LocalFileSystemMediaStore( + localStorageConfig: LocalStorageConfig, + applicationConfig: ApplicationConfig +) : + MediaStore { + + private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/" + override suspend fun upload(path: Path, id: String): URI { + logger.info("START Media upload. {}", id) + val fileSavePath = buildSavePath(path, id) + + val fileSavePathString = fileSavePath.toAbsolutePath().toString() + logger.info("MEDIA save. path: {}", fileSavePathString) + + @Suppress("TooGenericExceptionCaught") + try { + path.copyTo(fileSavePath) + } catch (e: Exception) { + logger.warn("FAILED to Save the media.", e) + throw e + } + + logger.info("SUCCESS Media upload. {}", id) + return URI.create(publicUrl).resolve(id) + } + + private fun buildSavePath(savePath: Path, name: String): Path = savePath.resolve(name) + + companion object { + private val logger = LoggerFactory.getLogger(LocalFileSystemMediaStore::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhash.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhash.kt new file mode 100644 index 00000000..6ee2893d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhash.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.infrastructure.media.common + +import java.awt.image.BufferedImage + +interface GenerateBlurhash { + fun generateBlurhash(bufferedImage: BufferedImage): String +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhashImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhashImpl.kt new file mode 100644 index 00000000..cb269364 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/common/GenerateBlurhashImpl.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.core.infrastructure.media.common + +import io.trbl.blurhash.BlurHash +import org.springframework.stereotype.Component +import java.awt.image.BufferedImage + +@Component +class GenerateBlurhashImpl : GenerateBlurhash { + override fun generateBlurhash(bufferedImage: BufferedImage): String = BlurHash.encode(bufferedImage) +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/image/ImageIOImageProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/image/ImageIOImageProcessor.kt new file mode 100644 index 00000000..b297ff51 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/image/ImageIOImageProcessor.kt @@ -0,0 +1,78 @@ +package dev.usbharu.hideout.core.infrastructure.media.image + +import dev.usbharu.hideout.core.config.ImageIOImageConfig +import dev.usbharu.hideout.core.domain.model.media.FileType +import dev.usbharu.hideout.core.domain.model.media.MimeType +import dev.usbharu.hideout.core.external.media.MediaProcessor +import dev.usbharu.hideout.core.external.media.ProcessedMedia +import dev.usbharu.hideout.core.infrastructure.media.common.GenerateBlurhash +import net.coobird.thumbnailator.Thumbnails +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Component +import java.awt.Color +import java.awt.image.BufferedImage +import java.nio.file.Files +import java.nio.file.Path +import java.util.* +import javax.imageio.ImageIO +import kotlin.io.path.inputStream +import kotlin.io.path.outputStream + +@Component +@Qualifier("image") +class ImageIOImageProcessor( + private val imageIOImageConfig: ImageIOImageConfig, + private val blurhash: GenerateBlurhash +) : MediaProcessor { + override fun isSupported(mimeType: MimeType): Boolean = + mimeType.fileType == FileType.Image || mimeType.type == "image" + + override suspend fun process(path: Path, filename: String, mimeType: MimeType?): ProcessedMedia { + val read = ImageIO.read(path.inputStream()) + + val bufferedImage = BufferedImage(read.width, read.height, BufferedImage.TYPE_INT_RGB) + + val graphics = bufferedImage.createGraphics() + + graphics.drawImage(read, 0, 0, Color.BLACK, null) + + val tempFileName = UUID.randomUUID().toString() + val tempFile = Files.createTempFile(tempFileName, "tmp") + + val thumbnailPath = run { + val tempThumbnailFile = Files.createTempFile("thumbnail-$tempFileName", ".tmp") + + tempThumbnailFile.outputStream().use { + val write = ImageIO.write( + Thumbnails.of(bufferedImage) + .size(imageIOImageConfig.thumbnailsWidth, imageIOImageConfig.thumbnailsHeight) + .imageType(BufferedImage.TYPE_INT_RGB) + .asBufferedImage(), + imageIOImageConfig.format, + it + ) + tempThumbnailFile.takeIf { write } + } + } + + tempFile.outputStream().use { + if (ImageIO.write(bufferedImage, imageIOImageConfig.format, it).not()) { + logger.warn("Failed to save a temporary file. type: {} ,path: {}", imageIOImageConfig.format, tempFile) + throw Exception("Failed to save a temporary file.") + } + } + + return ProcessedMedia( + tempFile, + thumbnailPath, + FileType.Image, + MimeType("image", imageIOImageConfig.format, FileType.Image), + blurhash.generateBlurhash(bufferedImage) + ) + } + + companion object { + private val logger = LoggerFactory.getLogger(ImageIOImageProcessor::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/video/FFmpegVideoProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/video/FFmpegVideoProcessor.kt new file mode 100644 index 00000000..22417894 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/media/video/FFmpegVideoProcessor.kt @@ -0,0 +1,115 @@ +package dev.usbharu.hideout.core.infrastructure.media.video + +import dev.usbharu.hideout.core.config.FFmpegVideoConfig +import dev.usbharu.hideout.core.domain.model.media.FileType +import dev.usbharu.hideout.core.domain.model.media.MimeType +import dev.usbharu.hideout.core.external.media.MediaProcessor +import dev.usbharu.hideout.core.external.media.ProcessedMedia +import dev.usbharu.hideout.core.infrastructure.media.common.GenerateBlurhash +import org.bytedeco.javacv.FFmpegFrameFilter +import org.bytedeco.javacv.FFmpegFrameGrabber +import org.bytedeco.javacv.FFmpegFrameRecorder +import org.bytedeco.javacv.Java2DFrameConverter +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Component +import java.awt.image.BufferedImage +import java.nio.file.Files +import java.nio.file.Path +import javax.imageio.ImageIO +import kotlin.math.min + +@Component +@Qualifier("video") +class FFmpegVideoProcessor( + private val fFmpegVideoConfig: FFmpegVideoConfig, + private val generateBlurhash: GenerateBlurhash +) : MediaProcessor { + override fun isSupported(mimeType: MimeType): Boolean = + mimeType.fileType == FileType.Video || mimeType.type == "video" + + override suspend fun process(path: Path, filename: String, mimeType: MimeType?): ProcessedMedia { + val tempFile = Files.createTempFile("hideout-movie-processor-", ".tmp") + val thumbnailFile = Files.createTempFile("hideout-movie-thumbnail-generate-", ".tmp") + logger.info("START Convert Movie Media {}", filename) + var bufferedImage: BufferedImage? = null + FFmpegFrameGrabber(path.toFile()).use { grabber -> + grabber.start() + val width = min(fFmpegVideoConfig.maxWidth, grabber.imageWidth) + val height = min(fFmpegVideoConfig.maxHeight, grabber.imageHeight) + val frameRate = fFmpegVideoConfig.frameRate + + logger.debug("Movie Media Width {}, Height {}", width, height) + + FFmpegFrameFilter( + "fps=fps=$frameRate", + "anull", + width, + height, + grabber.audioChannels + ).use { filter -> + + filter.sampleFormat = grabber.sampleFormat + filter.sampleRate = grabber.sampleRate + filter.pixelFormat = grabber.pixelFormat + filter.frameRate = grabber.frameRate + filter.start() + + val videoBitRate = min(fFmpegVideoConfig.maxBitrate, (width * height * frameRate * 1 * 0.07).toInt()) + + logger.debug("Movie Media BitRate {}", videoBitRate) + + FFmpegFrameRecorder(tempFile.toFile(), width, height, grabber.audioChannels).use { + it.sampleRate = grabber.sampleRate + it.format = fFmpegVideoConfig.format + it.videoCodec = fFmpegVideoConfig.videoCodec + it.audioCodec = fFmpegVideoConfig.audioCodec + it.audioChannels = grabber.audioChannels + it.videoQuality = fFmpegVideoConfig.videoQuality + it.frameRate = frameRate.toDouble() + it.setVideoOption("preset", "ultrafast") + it.timestamp = 0 + it.gopSize = frameRate + it.videoBitrate = videoBitRate + it.start() + + val frameConverter = Java2DFrameConverter() + + while (true) { + val grab = grabber.grab() ?: break + + if (bufferedImage == null) { + bufferedImage = frameConverter.convert(grab) + } + + if (grab.image != null || grab.samples != null) { + filter.push(grab) + } + while (true) { + val frame = filter.pull() ?: break + it.record(frame) + } + } + + if (bufferedImage != null) { + ImageIO.write(bufferedImage, "jpeg", thumbnailFile.toFile()) + } + } + } + } + + logger.info("SUCCESS Convert Movie Media {}", filename) + + return ProcessedMedia( + tempFile, + thumbnailFile, + FileType.Video, + MimeType("video", fFmpegVideoConfig.format, FileType.Video), + bufferedImage?.let { generateBlurhash.generateBlurhash(it) } + ) + } + + companion object { + private val logger = LoggerFactory.getLogger(FFmpegVideoProcessor::class.java) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepository.kt deleted file mode 100644 index 348d3abd..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepository.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.infrastructure.mongorepository - -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import org.springframework.data.domain.Pageable -import org.springframework.data.mongodb.repository.MongoRepository - -@Suppress("LongParameterList", "FunctionMaxLength") -interface MongoTimelineRepository : MongoRepository { - fun findByUserId(id: Long): List - fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List - fun findByUserIdAndTimelineIdAndPostIdBetweenAndIsLocal( - userId: Long?, - timelineId: Long?, - postIdMin: Long?, - postIdMax: Long?, - isLocal: Boolean?, - pageable: Pageable - ): List -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepositoryWrapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepositoryWrapper.kt deleted file mode 100644 index 58d36a36..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoTimelineRepositoryWrapper.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.infrastructure.mongorepository - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.dao.DataAccessException -import org.springframework.dao.DuplicateKeyException -import org.springframework.stereotype.Repository - -@Repository -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) -class MongoTimelineRepositoryWrapper( - private val mongoTimelineRepository: MongoTimelineRepository, - private val idGenerateService: IdGenerateService -) : - TimelineRepository { - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(timeline: Timeline): Timeline { - return withContext(Dispatchers.IO) { - mongoTimelineRepository.save(timeline) - } - } - - override suspend fun saveAll(timelines: List): List { - try { - return mongoTimelineRepository.saveAll(timelines) - } catch (e: DuplicateKeyException) { - throw DuplicateException("Timeline duplicate.", e) - } catch (e: DataAccessException) { - throw ResourceAccessException(e) - } - } - - override suspend fun findByUserId(id: Long): List { - return withContext(Dispatchers.IO) { - mongoTimelineRepository.findByUserId(id) - } - } - - override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List { - return withContext(Dispatchers.IO) { - mongoTimelineRepository.findByUserIdAndTimelineId(userId, timelineId) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/SnowflakeIdGenerateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/SnowflakeIdGenerateService.kt similarity index 92% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/SnowflakeIdGenerateService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/SnowflakeIdGenerateService.kt index d0748acb..3b82b1cc 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/SnowflakeIdGenerateService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/SnowflakeIdGenerateService.kt @@ -14,15 +14,16 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.service.id +package dev.usbharu.hideout.core.infrastructure.other +import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.time.Instant @Suppress("MagicNumber") -class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService { +open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService { var lastTimeStamp: Long = -1 var sequenceId: Int = 0 val mutex = Mutex() diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateService.kt similarity index 94% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateService.kt index a23d92d5..dcefc995 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateService.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.service.id +package dev.usbharu.hideout.core.infrastructure.other import org.springframework.context.annotation.Primary import org.springframework.stereotype.Service diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/DelegateCommandExecutorFactory.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/DelegateCommandExecutorFactory.kt new file mode 100644 index 00000000..cedebc83 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/DelegateCommandExecutorFactory.kt @@ -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.infrastructure.springframework + +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.jwt.Jwt +import org.springframework.stereotype.Component + +@Component +class DelegateCommandExecutorFactory( + private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, + private val mvcCommandExecutorFactory: SpringMvcCommandExecutorFactory, +) { + fun getCommandExecutor(): CommandExecutor { + if (SecurityContextHolder.getContext().authentication.principal is Jwt) { + return oauth2CommandExecutorFactory.getCommandExecutor() + } + return mvcCommandExecutorFactory.getCommandExecutor() + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/HttpCommandExecutor.kt similarity index 61% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/HttpCommandExecutor.kt index 77bf6287..e7b28ed5 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/HttpCommandExecutor.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.query +package dev.usbharu.hideout.core.infrastructure.springframework -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.core.domain.model.post.Post -import org.springframework.stereotype.Repository +import dev.usbharu.hideout.core.application.shared.CommandExecutor -@Repository -interface AnnounceQueryService { - suspend fun findById(id: Long): Pair? - suspend fun findByApId(apId: String): Pair? +open class HttpCommandExecutor( + override val executor: String, + val ip: String, + val userAgent: String, +) : CommandExecutor { + override fun toString(): String = "HttpCommandExecutor(executor='$executor', ip='$ip', userAgent='$userAgent')" } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringMvcCommandExecutorFactory.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringMvcCommandExecutorFactory.kt new file mode 100644 index 00000000..7a9b5940 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringMvcCommandExecutorFactory.kt @@ -0,0 +1,31 @@ +/* + * 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.infrastructure.springframework + +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.context.request.ServletRequestAttributes + +@Component +class SpringMvcCommandExecutorFactory { + fun getCommandExecutor(): HttpCommandExecutor { + val name = SecurityContextHolder.getContext().authentication?.name ?: "ANONYMOUS" + val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request + return HttpCommandExecutor(name, request.remoteAddr, request.getHeader("user-agent").orEmpty()) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/UpdateActorTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringSecurityPasswordEncoder.kt similarity index 63% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/UpdateActorTask.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringSecurityPasswordEncoder.kt index 5fc1a272..799a8d44 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/UpdateActorTask.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SpringSecurityPasswordEncoder.kt @@ -14,19 +14,15 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.external.job +package dev.usbharu.hideout.core.infrastructure.springframework -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition +import dev.usbharu.hideout.core.domain.service.userdetail.PasswordEncoder import org.springframework.stereotype.Component -data class UpdateActorTask( - val id: Long, - val apId: String, -) : Task() - @Component -data object UpdateActorTaskDef : TaskDefinition { - override val type: Class - get() = UpdateActorTask::class.java +class SpringSecurityPasswordEncoder( + private val passwordEncoder: org.springframework.security.crypto.password.PasswordEncoder, +) : + PasswordEncoder { + override suspend fun encode(input: String): String = passwordEncoder.encode(input) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteTask.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/domainevent/SpringFrameworkDomainEventPublisher.kt similarity index 54% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteTask.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/domainevent/SpringFrameworkDomainEventPublisher.kt index 6ce63ad2..ecbe2c2b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteTask.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/domainevent/SpringFrameworkDomainEventPublisher.kt @@ -14,21 +14,17 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.external.job +package dev.usbharu.hideout.core.infrastructure.springframework.domainevent -import dev.usbharu.hideout.activitypub.domain.model.Delete -import dev.usbharu.owl.common.task.Task -import dev.usbharu.owl.common.task.TaskDefinition +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Component -data class DeliverDeleteTask( - val delete: Delete, - val inbox: String, - val signer: Long, -) : Task() - @Component -data object DeliverDeleteTaskDef : TaskDefinition { - override val type: Class - get() = DeliverDeleteTask::class.java +class SpringFrameworkDomainEventPublisher(private val applicationEventPublisher: ApplicationEventPublisher) : + DomainEventPublisher { + override suspend fun publishEvent(domainEvent: DomainEvent) { + applicationEventPublisher.publishEvent(domainEvent) + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt deleted file mode 100644 index 27886cfb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.infrastructure.springframework.httpsignature - -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.verify.SignatureHeaderParser -import jakarta.servlet.http.HttpServletRequest -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter -import java.net.URL - -class HttpSignatureFilter( - private val httpSignatureHeaderParser: SignatureHeaderParser, - private val httpSignatureHeaderChecker: HttpSignatureHeaderChecker, -) : - AbstractPreAuthenticatedProcessingFilter() { - override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any? { - val headersList = request?.headerNames?.toList().orEmpty() - - val headers = - headersList.associateWith { header -> request?.getHeaders(header)?.toList().orEmpty() } - - val signature = try { - httpSignatureHeaderParser.parse(HttpHeaders(headers)) - } catch (_: IllegalArgumentException) { - return null - } catch (_: RuntimeException) { - return "" - } - return signature.keyId - } - - override fun getPreAuthenticatedCredentials(request: HttpServletRequest?): Any? { - requireNotNull(request) - val url = request.requestURL.toString() - - val headersList = request.headerNames?.toList().orEmpty() - - val headers = - headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() } - - val method = when (val method = request.method.lowercase()) { - "get" -> HttpMethod.GET - "post" -> HttpMethod.POST - else -> { -// throw IllegalArgumentException("Unsupported method: $method") - return null - } - } - - try { - httpSignatureHeaderChecker.checkDate(request.getHeader("date")!!) - httpSignatureHeaderChecker.checkHost(request.getHeader("host")!!) - if (request.method.equals("post", true)) { - httpSignatureHeaderChecker.checkDigest( - request.inputStream.readAllBytes()!!, - request.getHeader("digest")!! - ) - } - } catch (_: NullPointerException) { - return null - } catch (_: IllegalArgumentException) { - return null - } - - return HttpRequest( - URL(url + request.queryString.orEmpty()), - HttpHeaders(headers), - method - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderChecker.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderChecker.kt deleted file mode 100644 index eab673cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderChecker.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.infrastructure.springframework.httpsignature - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.util.Base64Util -import org.springframework.stereotype.Component -import java.security.MessageDigest -import java.time.Instant -import java.time.format.DateTimeFormatter -import java.util.* - -@Component -class HttpSignatureHeaderChecker(private val applicationConfig: ApplicationConfig) { - fun checkDate(date: String) { - val from = Instant.from(dateFormat.parse(date)) - - if (from.isAfter(Instant.now()) || from.isBefore(Instant.now().minusSeconds(86400))) { - throw IllegalArgumentException("未来") - } - } - - fun checkHost(host: String) { - if (applicationConfig.url.host.equals(host, true).not()) { - throw IllegalArgumentException("ホスト名が違う") - } - } - - fun checkDigest(byteArray: ByteArray, digest: String) { - val find = regex.find(digest) - val sha256 = MessageDigest.getInstance("SHA-256") - - val other = find?.groups?.get(2)?.value.orEmpty() - - if (Base64Util.encode(sha256.digest(byteArray)).equals(other, true).not()) { - throw IllegalArgumentException("リクエストボディが違う") - } - } - - companion object { - private val dateFormat = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - private val regex = Regex("^([a-zA-Z0-9\\-]+)=(.+)$") - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUser.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUser.kt deleted file mode 100644 index 50fc2c4e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUser.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.infrastructure.springframework.httpsignature - -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.core.userdetails.User -import java.io.Serial - -class HttpSignatureUser( - username: String, - val domain: String, - val id: Long, - credentialsNonExpired: Boolean, - accountNonLocked: Boolean, - authorities: MutableCollection? -) : User( - username, - "", - true, - true, - credentialsNonExpired, - accountNonLocked, - authorities -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is HttpSignatureUser) return false - if (!super.equals(other)) return false - - if (domain != other.domain) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + domain.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String { - return "HttpSignatureUser(" + - "domain='$domain', " + - "id=$id" + - ")" + - " ${super.toString()}" - } - - companion object { - @Serial - private const val serialVersionUID: Long = -3330552099960982997L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt deleted file mode 100644 index 6ec16ddd..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.infrastructure.springframework.httpsignature - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.HttpSignatureVerifyException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.util.RsaUtil -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.common.PublicKey -import dev.usbharu.httpsignature.verify.FailedVerification -import dev.usbharu.httpsignature.verify.HttpSignatureVerifier -import dev.usbharu.httpsignature.verify.SignatureHeaderParser -import kotlinx.coroutines.runBlocking -import org.slf4j.LoggerFactory -import org.springframework.security.authentication.BadCredentialsException -import org.springframework.security.core.userdetails.AuthenticationUserDetailsService -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken - -class HttpSignatureUserDetailsService( - private val httpSignatureVerifier: HttpSignatureVerifier, - private val transaction: Transaction, - private val httpSignatureHeaderParser: SignatureHeaderParser, - private val actorRepository: ActorRepository -) : - AuthenticationUserDetailsService { - override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking { - check(token.principal is String) { "Token is not String" } - val credentials = token.credentials - - check(credentials is HttpRequest) { "Credentials is not HttpRequest" } - - val keyId = token.principal as String - val findByKeyId = transaction.transaction { - actorRepository.findByKeyId(keyId) ?: throw UsernameNotFoundException("keyId: $keyId not found.") - } - - val signature = httpSignatureHeaderParser.parse(credentials.headers) - - val requiredHeaders = when (credentials.method) { - HttpMethod.GET -> getRequiredHeaders - HttpMethod.POST -> postRequiredHeaders - } - if (signature.headers.containsAll(requiredHeaders).not()) { - logger.warn( - "FAILED Verify HTTP Signature. required headers: {} but actual: {}", - requiredHeaders, - signature.headers - ) - throw BadCredentialsException("HTTP Signature. required headers: $requiredHeaders") - } - - @Suppress("TooGenericExceptionCaught") - val verify = try { - httpSignatureVerifier.verify( - credentials, - PublicKey(RsaUtil.decodeRsaPublicKeyPem(findByKeyId.publicKey), keyId) - ) - } catch (e: RuntimeException) { - throw BadCredentialsException("", e) - } - - if (verify is FailedVerification) { - logger.warn("FAILED Verify HTTP Signature reason: {}", verify.reason) - throw HttpSignatureVerifyException(verify.reason) - } - - HttpSignatureUser( - username = findByKeyId.name, - domain = findByKeyId.domain, - id = findByKeyId.id, - credentialsNonExpired = true, - accountNonLocked = true, - authorities = mutableListOf() - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(HttpSignatureUserDetailsService::class.java) - private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") - private val getRequiredHeaders = listOf("(request-target)", "date", "host") - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureVerifierComposite.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureVerifierComposite.kt deleted file mode 100644 index 8dd83ee3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureVerifierComposite.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.infrastructure.springframework.httpsignature - -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.common.PublicKey -import dev.usbharu.httpsignature.verify.HttpSignatureVerifier -import dev.usbharu.httpsignature.verify.SignatureHeaderParser -import dev.usbharu.httpsignature.verify.VerificationResult - -class HttpSignatureVerifierComposite( - private val map: Map, - private val httpSignatureHeaderParser: SignatureHeaderParser -) : HttpSignatureVerifier { - override fun verify(httpRequest: HttpRequest, key: PublicKey): VerificationResult { - val signature = httpSignatureHeaderParser.parse(httpRequest.headers) - val verify = map[signature.algorithm]?.verify(httpRequest, key) - if (verify != null) { - return verify - } - - throw IllegalArgumentException("Unsupported algorithm. ${signature.algorithm}") - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HttpSignatureVerifierComposite - - if (map != other.map) return false - if (httpSignatureHeaderParser != other.httpSignatureHeaderParser) return false - - return true - } - - override fun hashCode(): Int { - var result = map.hashCode() - result = 31 * result + httpSignatureHeaderParser.hashCode() - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationConsentService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationConsentService.kt deleted file mode 100644 index 4ad94f0e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationConsentService.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.infrastructure.springframework.oauth2 - -import dev.usbharu.hideout.application.external.Transaction -import kotlinx.coroutines.runBlocking -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository -import org.springframework.stereotype.Service -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent as AuthorizationConsent - -@Service -class ExposedOAuth2AuthorizationConsentService( - private val registeredClientRepository: RegisteredClientRepository, - private val transaction: Transaction, -) : - OAuth2AuthorizationConsentService { - - override fun save(authorizationConsent: AuthorizationConsent?): Unit = runBlocking { - requireNotNull(authorizationConsent) - transaction.transaction { - val singleOrNull = - OAuth2AuthorizationConsent.selectAll().where { - OAuth2AuthorizationConsent.registeredClientId - .eq(authorizationConsent.registeredClientId) - .and(OAuth2AuthorizationConsent.principalName.eq(authorizationConsent.principalName)) - } - .singleOrNull() - if (singleOrNull == null) { - OAuth2AuthorizationConsent.insert { - it[registeredClientId] = authorizationConsent.registeredClientId - it[principalName] = authorizationConsent.principalName - it[authorities] = authorizationConsent.authorities.joinToString(",") - } - } - } - } - - override fun remove(authorizationConsent: AuthorizationConsent?) { - if (authorizationConsent == null) { - return - } - OAuth2AuthorizationConsent.deleteWhere { - registeredClientId eq authorizationConsent.registeredClientId and (principalName eq principalName) - } - } - - override fun findById(registeredClientId: String?, principalName: String?): AuthorizationConsent? = runBlocking { - requireNotNull(registeredClientId) - requireNotNull(principalName) - transaction.transaction { - OAuth2AuthorizationConsent.selectAll().where { - (OAuth2AuthorizationConsent.registeredClientId eq registeredClientId) - .and(OAuth2AuthorizationConsent.principalName eq principalName) - } - .singleOrNull()?.toAuthorizationConsent() - } - } - - fun ResultRow.toAuthorizationConsent(): AuthorizationConsent { - val registeredClientId = this[OAuth2AuthorizationConsent.registeredClientId] - registeredClientRepository.findById(registeredClientId) - - val principalName = this[OAuth2AuthorizationConsent.principalName] - val builder = AuthorizationConsent.withId(registeredClientId, principalName) - - this[OAuth2AuthorizationConsent.authorities].split(",").forEach { - builder.authority(SimpleGrantedAuthority(it)) - } - - return builder.build() - } -} - -object OAuth2AuthorizationConsent : Table("oauth2_authorization_consent") { - val registeredClientId: Column = varchar("registered_client_id", 100) - val principalName: Column = varchar("principal_name", 200) - val authorities: Column = varchar("authorities", 1000) - override val primaryKey: PrimaryKey = PrimaryKey(registeredClientId, principalName) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationService.kt deleted file mode 100644 index 4cb43890..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/ExposedOAuth2AuthorizationService.kt +++ /dev/null @@ -1,393 +0,0 @@ -/* - * 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.infrastructure.springframework.oauth2 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.external.Transaction -import kotlinx.coroutines.runBlocking -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.springframework.security.jackson2.CoreJackson2Module -import org.springframework.security.jackson2.SecurityJackson2Modules -import org.springframework.security.oauth2.core.* -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames -import org.springframework.security.oauth2.core.oidc.OidcIdToken -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class ExposedOAuth2AuthorizationService( - private val registeredClientRepository: RegisteredClientRepository, - private val transaction: Transaction, -) : - OAuth2AuthorizationService { - - @Suppress("LongMethod", "CyclomaticComplexMethod") - override fun save(authorization: OAuth2Authorization?): Unit = runBlocking { - requireNotNull(authorization) - transaction.transaction { - val singleOrNull = Authorization.selectAll().where { Authorization.id eq authorization.id }.singleOrNull() - if (singleOrNull == null) { - val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java) - val accessToken = authorization.getToken(OAuth2AccessToken::class.java) - val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java) - val oidcIdToken = authorization.getToken(OidcIdToken::class.java) - val userCode = authorization.getToken(OAuth2UserCode::class.java) - val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java) - Authorization.insert { - it[id] = authorization.id - it[registeredClientId] = authorization.registeredClientId - it[principalName] = authorization.principalName - it[authorizationGrantType] = authorization.authorizationGrantType.value - it[authorizedScopes] = - authorization.authorizedScopes.joinToString(",").takeIf { s -> s.isNotEmpty() } - it[attributes] = mapToJson(authorization.attributes) - it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE) - it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue - it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt - it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt - it[authorizationCodeMetadata] = - authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) } - it[accessTokenValue] = accessToken?.token?.tokenValue - it[accessTokenIssuedAt] = accessToken?.token?.issuedAt - it[accessTokenExpiresAt] = accessToken?.token?.expiresAt - it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) } - it[accessTokenType] = accessToken?.token?.tokenType?.value - it[accessTokenScopes] = - accessToken?.run { token.scopes.joinToString(",").takeIf { s -> s.isNotEmpty() } } - it[refreshTokenValue] = refreshToken?.token?.tokenValue - it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt - it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt - it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) } - it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue - it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt - it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt - it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) } - it[userCodeValue] = userCode?.token?.tokenValue - it[userCodeIssuedAt] = userCode?.token?.issuedAt - it[userCodeExpiresAt] = userCode?.token?.expiresAt - it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) } - it[deviceCodeValue] = deviceCode?.token?.tokenValue - it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt - it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt - it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) } - } - } else { - val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java) - val accessToken = authorization.getToken(OAuth2AccessToken::class.java) - val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java) - val oidcIdToken = authorization.getToken(OidcIdToken::class.java) - val userCode = authorization.getToken(OAuth2UserCode::class.java) - val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java) - Authorization.update({ Authorization.id eq authorization.id }) { - it[registeredClientId] = authorization.registeredClientId - it[principalName] = authorization.principalName - it[authorizationGrantType] = authorization.authorizationGrantType.value - it[authorizedScopes] = - authorization.authorizedScopes.joinToString(",").takeIf { s -> s.isNotEmpty() } - it[attributes] = mapToJson(authorization.attributes) - it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE) - it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue - it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt - it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt - it[authorizationCodeMetadata] = - authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) } - it[accessTokenValue] = accessToken?.token?.tokenValue - it[accessTokenIssuedAt] = accessToken?.token?.issuedAt - it[accessTokenExpiresAt] = accessToken?.token?.expiresAt - it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) } - it[accessTokenType] = accessToken?.run { token.tokenType.value } - it[accessTokenScopes] = - accessToken?.run { token.scopes.joinToString(",").takeIf { s -> s.isNotEmpty() } } - it[refreshTokenValue] = refreshToken?.token?.tokenValue - it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt - it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt - it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) } - it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue - it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt - it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt - it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) } - it[userCodeValue] = userCode?.token?.tokenValue - it[userCodeIssuedAt] = userCode?.token?.issuedAt - it[userCodeExpiresAt] = userCode?.token?.expiresAt - it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) } - it[deviceCodeValue] = deviceCode?.token?.tokenValue - it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt - it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt - it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) } - } - } - } - } - - override fun remove(authorization: OAuth2Authorization?) { - if (authorization == null) { - return - } - Authorization.deleteWhere { id eq authorization.id } - } - - override fun findById(id: String?): OAuth2Authorization? { - if (id == null) { - return null - } - return Authorization.selectAll().where { Authorization.id eq id }.singleOrNull()?.toAuthorization() - } - - override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? = runBlocking { - requireNotNull(token) - transaction.transaction { - when (tokenType?.value) { - null -> { - Authorization.selectAll().where { Authorization.authorizationCodeValue eq token }.orWhere { - Authorization.accessTokenValue eq token - }.orWhere { - Authorization.oidcIdTokenValue eq token - }.orWhere { - Authorization.refreshTokenValue eq token - }.orWhere { - Authorization.userCodeValue eq token - }.orWhere { - Authorization.deviceCodeValue eq token - } - } - - OAuth2ParameterNames.STATE -> { - Authorization.selectAll().where { Authorization.state eq token } - } - - OAuth2ParameterNames.CODE -> { - Authorization.selectAll().where { Authorization.authorizationCodeValue eq token } - } - - OAuth2ParameterNames.ACCESS_TOKEN -> { - Authorization.selectAll().where { Authorization.accessTokenValue eq token } - } - - OidcParameterNames.ID_TOKEN -> { - Authorization.selectAll().where { Authorization.oidcIdTokenValue eq token } - } - - OAuth2ParameterNames.REFRESH_TOKEN -> { - Authorization.selectAll().where { Authorization.refreshTokenValue eq token } - } - - OAuth2ParameterNames.USER_CODE -> { - Authorization.selectAll().where { Authorization.userCodeValue eq token } - } - - OAuth2ParameterNames.DEVICE_CODE -> { - Authorization.selectAll().where { Authorization.deviceCodeValue eq token } - } - - else -> { - null - } - }?.singleOrNull()?.toAuthorization() - } - } - - @Suppress("LongMethod", "CyclomaticComplexMethod", "CastToNullableType", "UNCHECKED_CAST") - fun ResultRow.toAuthorization(): OAuth2Authorization { - val registeredClientId = this[Authorization.registeredClientId] - - val registeredClient = registeredClientRepository.findById(registeredClientId) - - val builder = OAuth2Authorization.withRegisteredClient(registeredClient) - val id = this[Authorization.id] - val principalName = this[Authorization.principalName] - val authorizationGrantType = this[Authorization.authorizationGrantType] - val authorizedScopes = this[Authorization.authorizedScopes]?.split(",").orEmpty().toSet() - val attributes = this[Authorization.attributes]?.let { jsonToMap(it) }.orEmpty() - - builder.id(id).principalName(principalName) - .authorizationGrantType(AuthorizationGrantType(authorizationGrantType)).authorizedScopes(authorizedScopes) - .attributes { it.putAll(attributes) } - - val state = this[Authorization.state].orEmpty() - if (state.isNotBlank()) { - builder.attribute(OAuth2ParameterNames.STATE, state) - } - - val authorizationCodeValue = this[Authorization.authorizationCodeValue].orEmpty() - if (authorizationCodeValue.isNotBlank()) { - val authorizationCodeIssuedAt = this[Authorization.authorizationCodeIssuedAt] - val authorizationCodeExpiresAt = this[Authorization.authorizationCodeExpiresAt] - val authorizationCodeMetadata = this[Authorization.authorizationCodeMetadata]?.let { - jsonToMap( - it - ) - }.orEmpty() - val oAuth2AuthorizationCode = - OAuth2AuthorizationCode(authorizationCodeValue, authorizationCodeIssuedAt, authorizationCodeExpiresAt) - builder.token(oAuth2AuthorizationCode) { - it.putAll(authorizationCodeMetadata) - } - } - - val accessTokenValue = this[Authorization.accessTokenValue].orEmpty() - if (accessTokenValue.isNotBlank()) { - val accessTokenIssuedAt = this[Authorization.accessTokenIssuedAt] - val accessTokenExpiresAt = this[Authorization.accessTokenExpiresAt] - val accessTokenMetadata = - this[Authorization.accessTokenMetadata]?.let { jsonToMap(it) }.orEmpty() - val accessTokenType = - if (this[Authorization.accessTokenType].equals(OAuth2AccessToken.TokenType.BEARER.value, true)) { - OAuth2AccessToken.TokenType.BEARER - } else { - null - } - - val accessTokenScope = this[Authorization.accessTokenScopes]?.split(",").orEmpty().toSet() - - val oAuth2AccessToken = OAuth2AccessToken( - accessTokenType, - accessTokenValue, - accessTokenIssuedAt, - accessTokenExpiresAt, - accessTokenScope - ) - - builder.token(oAuth2AccessToken) { it.putAll(accessTokenMetadata) } - } - - val oidcIdTokenValue = this[Authorization.oidcIdTokenValue].orEmpty() - if (oidcIdTokenValue.isNotBlank()) { - val oidcTokenIssuedAt = this[Authorization.oidcIdTokenIssuedAt] - val oidcTokenExpiresAt = this[Authorization.oidcIdTokenExpiresAt] - val oidcTokenMetadata = - this[Authorization.oidcIdTokenMetadata]?.let { jsonToMap(it) }.orEmpty() - - val oidcIdToken = OidcIdToken( - oidcIdTokenValue, - oidcTokenIssuedAt, - oidcTokenExpiresAt, - oidcTokenMetadata.getValue(OAuth2Authorization.Token.CLAIMS_METADATA_NAME) - as MutableMap? - ) - - builder.token(oidcIdToken) { it.putAll(oidcTokenMetadata) } - } - - val refreshTokenValue = this[Authorization.refreshTokenValue].orEmpty() - if (refreshTokenValue.isNotBlank()) { - val refreshTokenIssuedAt = this[Authorization.refreshTokenIssuedAt] - val refreshTokenExpiresAt = this[Authorization.refreshTokenExpiresAt] - val refreshTokenMetadata = - this[Authorization.refreshTokenMetadata]?.let { jsonToMap(it) }.orEmpty() - - val oAuth2RefreshToken = OAuth2RefreshToken(refreshTokenValue, refreshTokenIssuedAt, refreshTokenExpiresAt) - - builder.token(oAuth2RefreshToken) { it.putAll(refreshTokenMetadata) } - } - - val userCodeValue = this[Authorization.userCodeValue].orEmpty() - if (userCodeValue.isNotBlank()) { - val userCodeIssuedAt = this[Authorization.userCodeIssuedAt] - val userCodeExpiresAt = this[Authorization.userCodeExpiresAt] - val userCodeMetadata = - this[Authorization.userCodeMetadata]?.let { jsonToMap(it) }.orEmpty() - val oAuth2UserCode = OAuth2UserCode(userCodeValue, userCodeIssuedAt, userCodeExpiresAt) - builder.token(oAuth2UserCode) { it.putAll(userCodeMetadata) } - } - - val deviceCodeValue = this[Authorization.deviceCodeValue].orEmpty() - if (deviceCodeValue.isNotBlank()) { - val deviceCodeIssuedAt = this[Authorization.deviceCodeIssuedAt] - val deviceCodeExpiresAt = this[Authorization.deviceCodeExpiresAt] - val deviceCodeMetadata = - this[Authorization.deviceCodeMetadata]?.let { jsonToMap(it) }.orEmpty() - - val oAuth2DeviceCode = OAuth2DeviceCode(deviceCodeValue, deviceCodeIssuedAt, deviceCodeExpiresAt) - builder.token(oAuth2DeviceCode) { it.putAll(deviceCodeMetadata) } - } - - return builder.build() - } - - private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map) - - private fun jsonToMap(json: String): Map = objectMapper.readValue(json) - - companion object { - val objectMapper: ObjectMapper = ObjectMapper() - - init { - - val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader - val modules = SecurityJackson2Modules.getModules(classLoader) - objectMapper.registerModules(JavaTimeModule()) - objectMapper.registerModules(modules) - objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module()) - objectMapper.registerModules(CoreJackson2Module()) - objectMapper.addMixIn(UserDetailsImpl::class.java, UserDetailsMixin::class.java) - } - } -} - -object Authorization : Table("application_authorization") { - val id: Column = varchar("id", 255) - val registeredClientId: Column = varchar("registered_client_id", 255) - val principalName: Column = varchar("principal_name", 255) - val authorizationGrantType: Column = varchar("authorization_grant_type", 255) - val authorizedScopes: Column = varchar("authorized_scopes", 1000).nullable().default(null) - val attributes: Column = varchar("attributes", 4000).nullable().default(null) - val state: Column = varchar("state", 500).nullable().default(null) - val authorizationCodeValue: Column = varchar("authorization_code_value", 4000).nullable().default(null) - val authorizationCodeIssuedAt: Column = timestamp("authorization_code_issued_at").nullable().default(null) - val authorizationCodeExpiresAt: Column = timestamp("authorization_code_expires_at").nullable().default( - null - ) - val authorizationCodeMetadata: Column = varchar("authorization_code_metadata", 2000).nullable().default( - null - ) - val accessTokenValue: Column = varchar("access_token_value", 4000).nullable().default(null) - val accessTokenIssuedAt: Column = timestamp("access_token_issued_at").nullable().default(null) - val accessTokenExpiresAt: Column = timestamp("access_token_expires_at").nullable().default(null) - val accessTokenMetadata: Column = varchar("access_token_metadata", 2000).nullable().default(null) - val accessTokenType: Column = varchar("access_token_type", 255).nullable().default(null) - val accessTokenScopes: Column = varchar("access_token_scopes", 1000).nullable().default(null) - val refreshTokenValue: Column = varchar("refresh_token_value", 4000).nullable().default(null) - val refreshTokenIssuedAt: Column = timestamp("refresh_token_issued_at").nullable().default(null) - val refreshTokenExpiresAt: Column = timestamp("refresh_token_expires_at").nullable().default(null) - val refreshTokenMetadata: Column = varchar("refresh_token_metadata", 2000).nullable().default(null) - val oidcIdTokenValue: Column = varchar("oidc_id_token_value", 4000).nullable().default(null) - val oidcIdTokenIssuedAt: Column = timestamp("oidc_id_token_issued_at").nullable().default(null) - val oidcIdTokenExpiresAt: Column = timestamp("oidc_id_token_expires_at").nullable().default(null) - val oidcIdTokenMetadata: Column = varchar("oidc_id_token_metadata", 2000).nullable().default(null) - val oidcIdTokenClaims: Column = varchar("oidc_id_token_claims", 2000).nullable().default(null) - val userCodeValue: Column = varchar("user_code_value", 4000).nullable().default(null) - val userCodeIssuedAt: Column = timestamp("user_code_issued_at").nullable().default(null) - val userCodeExpiresAt: Column = timestamp("user_code_expires_at").nullable().default(null) - val userCodeMetadata: Column = varchar("user_code_metadata", 2000).nullable().default(null) - val deviceCodeValue: Column = varchar("device_code_value", 4000).nullable().default(null) - val deviceCodeIssuedAt: Column = timestamp("device_code_issued_at").nullable().default(null) - val deviceCodeExpiresAt: Column = timestamp("device_code_expires_at").nullable().default(null) - val deviceCodeMetadata: Column = varchar("device_code_metadata", 2000).nullable().default(null) - - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutJdbcOauth2AuthorizationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutJdbcOauth2AuthorizationService.kt new file mode 100644 index 00000000..2044962e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutJdbcOauth2AuthorizationService.kt @@ -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.infrastructure.springframework.oauth2 + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.jdbc.core.JdbcOperations +import org.springframework.jdbc.support.lob.DefaultLobHandler +import org.springframework.jdbc.support.lob.LobHandler +import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.stereotype.Component + +@Component +class HideoutJdbcOauth2AuthorizationService( + registeredClientRepository: RegisteredClientRepository, + jdbcOperations: JdbcOperations, + @Autowired(required = false) lobHandler: LobHandler = DefaultLobHandler(), +) : JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository, lobHandler) { + + init { + super.setAuthorizationRowMapper( + HideoutOAuth2AuthorizationRowMapper(registeredClientRepository = registeredClientRepository) + ) + } + + class HideoutOAuth2AuthorizationRowMapper(registeredClientRepository: RegisteredClientRepository?) : + OAuth2AuthorizationRowMapper(registeredClientRepository) { + init { + objectMapper.addMixIn(HideoutUserDetails::class.java, UserDetailsMixin::class.java) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutUserDetails.kt similarity index 65% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutUserDetails.kt index abb9846e..616868fe 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/HideoutUserDetails.kt @@ -29,45 +29,57 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.annotation.JsonDeserialize import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails import java.io.Serial +import java.util.* -class UserDetailsImpl( - val id: Long, - username: String?, - password: String?, - enabled: Boolean, - accountNonExpired: Boolean, - credentialsNonExpired: Boolean, - accountNonLocked: Boolean, - authorities: MutableCollection? -) : User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities) { - override fun toString(): String { - return "UserDetailsImpl(" + - "id=$id" + - ")" + - " ${super.toString()}" - } +class HideoutUserDetails( + authorities: Set, + private val password: String, + private val username: String, + val userDetailsId: Long, +) : UserDetails { + private val authorities: MutableSet = Collections.unmodifiableSet(authorities) + override fun getAuthorities(): MutableSet = authorities + + override fun getPassword(): String = password + + override fun getUsername(): String = username override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - other as UserDetailsImpl + other as HideoutUserDetails - return id == other.id + if (authorities != other.authorities) return false + if (password != other.password) return false + if (username != other.username) return false + if (userDetailsId != other.userDetailsId) return false + + return true } override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() + var result = authorities.hashCode() + result = 31 * result + password.hashCode() + result = 31 * result + username.hashCode() + result = 31 * result + userDetailsId.hashCode() return result } + override fun toString(): String { + return "HideoutUserDetails(" + + "password='$password', " + + "username='$username', " + + "userDetailsId=$userDetailsId, " + + "authorities=$authorities" + + ")" + } + companion object { @Serial - private const val serialVersionUID: Long = -899168205656607781L + private const val serialVersionUID = -899168205656607781L } } @@ -84,9 +96,9 @@ class UserDetailsImpl( @Suppress("UnnecessaryAbstractClass") abstract class UserDetailsMixin -class UserDetailsDeserializer : JsonDeserializer() { +class UserDetailsDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): HideoutUserDetails { val mapper = p.codec as ObjectMapper val jsonNode: JsonNode = mapper.readTree(p) val authorities: Set = mapper.convertValue( @@ -95,15 +107,11 @@ class UserDetailsDeserializer : JsonDeserializer() { ) val password = jsonNode.readText("password") - return UserDetailsImpl( - id = jsonNode["id"].longValue(), + return HideoutUserDetails( + userDetailsId = jsonNode["userDetailsId"].longValue(), username = jsonNode.readText("username"), password = password, - enabled = true, - accountNonExpired = true, - credentialsNonExpired = true, - accountNonLocked = true, - authorities = authorities.toMutableList(), + authorities = authorities ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/LoginUserContextHolder.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt similarity index 68% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/LoginUserContextHolder.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt index fc6a3c42..9cf58a40 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/LoginUserContextHolder.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.infrastructure.springframework.security +package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 -interface LoginUserContextHolder { - fun getLoginUserId(): Long +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor - fun getLoginUserIdOrNull(): Long? -} +class Oauth2CommandExecutor(override val executor: String, override val userDetailId: Long) : + CommandExecutor, + UserDetailGettableCommandExecutor diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/OAuth2JwtLoginUserContextHolder.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutorFactory.kt similarity index 67% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/OAuth2JwtLoginUserContextHolder.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutorFactory.kt index 29746d90..1416a2a3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/security/OAuth2JwtLoginUserContextHolder.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutorFactory.kt @@ -14,26 +14,20 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.infrastructure.springframework.security +package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.oauth2.jwt.Jwt import org.springframework.stereotype.Component @Component -class OAuth2JwtLoginUserContextHolder : LoginUserContextHolder { - override fun getLoginUserId(): Long { +class Oauth2CommandExecutorFactory { + fun getCommandExecutor(): Oauth2CommandExecutor { val principal = SecurityContextHolder.getContext().authentication.principal as Jwt - return principal.getClaim("uid").toLong() - } - - override fun getLoginUserIdOrNull(): Long? { - val principal = SecurityContextHolder.getContext()?.authentication?.principal - if (principal !is Jwt) { - return null - } - - return principal.getClaim("uid").toLongOrNull() + return Oauth2CommandExecutor( + principal.subject, + principal.getClaim("uid").toLong() + ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/RegisteredClientRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/RegisteredClientRepositoryImpl.kt deleted file mode 100644 index 68a3177d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/RegisteredClientRepositoryImpl.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.infrastructure.springframework.oauth2 - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.clientId -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.clientSettings -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.RegisteredClient.tokenSettings -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.CurrentTimestamp -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.security.jackson2.SecurityJackson2Modules -import org.springframework.security.oauth2.core.AuthorizationGrantType -import org.springframework.security.oauth2.core.ClientAuthenticationMethod -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings -import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings -import org.springframework.stereotype.Repository -import org.springframework.transaction.annotation.Transactional -import java.time.Instant -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient as SpringRegisteredClient - -@Repository -class RegisteredClientRepositoryImpl : RegisteredClientRepository { - - override fun save(registeredClient: SpringRegisteredClient?) { - requireNotNull(registeredClient) - val singleOrNull = - RegisteredClient.selectAll().where { RegisteredClient.id eq registeredClient.id }.singleOrNull() - if (singleOrNull == null) { - RegisteredClient.insert { - it[id] = registeredClient.id - it[clientId] = registeredClient.clientId - it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now() - it[clientSecret] = registeredClient.clientSecret - it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt - it[clientName] = registeredClient.clientName - it[clientAuthenticationMethods] = - registeredClient.clientAuthenticationMethods.joinToString(",") { method -> method.value } - it[authorizationGrantTypes] = - registeredClient.authorizationGrantTypes.joinToString(",") { type -> type.value } - it[redirectUris] = registeredClient.redirectUris.joinToString(",") - it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",") - it[scopes] = registeredClient.scopes.joinToString(",") - it[clientSettings] = mapToJson(registeredClient.clientSettings.settings) - it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings) - } - } else { - RegisteredClient.update({ RegisteredClient.id eq registeredClient.id }) { - it[clientId] = registeredClient.clientId - it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now() - it[clientSecret] = registeredClient.clientSecret - it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt - it[clientName] = registeredClient.clientName - it[clientAuthenticationMethods] = registeredClient.clientAuthenticationMethods.joinToString(",") - it[authorizationGrantTypes] = registeredClient.authorizationGrantTypes.joinToString(",") - it[redirectUris] = registeredClient.redirectUris.joinToString(",") - it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",") - it[scopes] = registeredClient.scopes.joinToString(",") - it[clientSettings] = mapToJson(registeredClient.clientSettings.settings) - it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings) - } - } - } - - override fun findById(id: String?): SpringRegisteredClient? { - if (id == null) { - return null - } - return RegisteredClient.selectAll().where { RegisteredClient.id eq id }.singleOrNull()?.toRegisteredClient() - } - - @Transactional - override fun findByClientId(clientId: String?): SpringRegisteredClient? { - if (clientId == null) { - return null - } - val toRegisteredClient = - RegisteredClient.selectAll().where { RegisteredClient.clientId eq clientId }.singleOrNull() - ?.toRegisteredClient() - LOGGER.trace("findByClientId: {}", toRegisteredClient) - return toRegisteredClient - } - - private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map) - - private fun jsonToMap(json: String): Map = objectMapper.readValue(json) - - @Suppress("CyclomaticComplexMethod") - fun ResultRow.toRegisteredClient(): SpringRegisteredClient { - fun resolveClientAuthenticationMethods(string: String): ClientAuthenticationMethod { - return when (string) { - ClientAuthenticationMethod.CLIENT_SECRET_BASIC.value -> ClientAuthenticationMethod.CLIENT_SECRET_BASIC - ClientAuthenticationMethod.CLIENT_SECRET_JWT.value -> ClientAuthenticationMethod.CLIENT_SECRET_JWT - ClientAuthenticationMethod.CLIENT_SECRET_POST.value -> ClientAuthenticationMethod.CLIENT_SECRET_POST - ClientAuthenticationMethod.NONE.value -> ClientAuthenticationMethod.NONE - else -> { - ClientAuthenticationMethod(string) - } - } - } - - fun resolveAuthorizationGrantType(string: String): AuthorizationGrantType { - return when (string) { - AuthorizationGrantType.AUTHORIZATION_CODE.value -> AuthorizationGrantType.AUTHORIZATION_CODE - AuthorizationGrantType.CLIENT_CREDENTIALS.value -> AuthorizationGrantType.CLIENT_CREDENTIALS - AuthorizationGrantType.REFRESH_TOKEN.value -> AuthorizationGrantType.REFRESH_TOKEN - else -> { - AuthorizationGrantType(string) - } - } - } - - val clientAuthenticationMethods = this[RegisteredClient.clientAuthenticationMethods].split(",").toSet() - val authorizationGrantTypes = this[RegisteredClient.authorizationGrantTypes].split(",").toSet() - val redirectUris = this[RegisteredClient.redirectUris]?.split(",").orEmpty().toSet() - val postLogoutRedirectUris = this[RegisteredClient.postLogoutRedirectUris]?.split(",").orEmpty().toSet() - val clientScopes = this[RegisteredClient.scopes].split(",").toSet() - - val builder = SpringRegisteredClient - .withId(this[RegisteredClient.id]) - .clientId(this[clientId]) - .clientIdIssuedAt(this[RegisteredClient.clientIdIssuedAt]) - .clientSecret(this[RegisteredClient.clientSecret]) - .clientSecretExpiresAt(this[RegisteredClient.clientSecretExpiresAt]) - .clientName(this[RegisteredClient.clientName]) - .clientAuthenticationMethods { - clientAuthenticationMethods.forEach { s -> - it.add(resolveClientAuthenticationMethods(s)) - } - } - .authorizationGrantTypes { - authorizationGrantTypes.forEach { s -> - it.add(resolveAuthorizationGrantType(s)) - } - } - .redirectUris { it.addAll(redirectUris) } - .postLogoutRedirectUris { it.addAll(postLogoutRedirectUris) } - .scopes { it.addAll(clientScopes) } - .clientSettings(ClientSettings.withSettings(jsonToMap(this[clientSettings])).build()) - - val tokenSettingsMap = jsonToMap(this[tokenSettings]) - val withSettings = TokenSettings.withSettings(tokenSettingsMap) - if (tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) { - withSettings.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) - } - builder.tokenSettings(withSettings.build()) - - return builder.build() - } - - companion object { - val objectMapper: ObjectMapper = ObjectMapper() - val LOGGER: Logger = LoggerFactory.getLogger(RegisteredClientRepositoryImpl::class.java) - - init { - - val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader - val modules = SecurityJackson2Modules.getModules(classLoader) - objectMapper.registerModules(JavaTimeModule()) - objectMapper.registerModules(modules) - objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module()) - } - } -} - -// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql -object RegisteredClient : Table("registered_client") { - val id: Column = varchar("id", 100) - val clientId: Column = varchar("client_id", 100) - val clientIdIssuedAt: Column = timestamp("client_id_issued_at").defaultExpression(CurrentTimestamp) - val clientSecret: Column = varchar("client_secret", 200).nullable().default(null) - val clientSecretExpiresAt: Column = timestamp("client_secret_expires_at").nullable().default(null) - val clientName: Column = varchar("client_name", 200) - val clientAuthenticationMethods: Column = varchar("client_authentication_methods", 1000) - val authorizationGrantTypes: Column = varchar("authorization_grant_types", 1000) - val redirectUris: Column = varchar("redirect_uris", 1000).nullable().default(null) - val postLogoutRedirectUris: Column = varchar("post_logout_redirect_uris", 1000).nullable().default(null) - val scopes: Column = varchar("scopes", 1000) - val clientSettings: Column = varchar("client_settings", 2000) - val tokenSettings: Column = varchar("token_settings", 2000) - - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt index 4a91c24a..5ca4964e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt @@ -16,45 +16,37 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException +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.userdetails.UserDetailRepository import kotlinx.coroutines.runBlocking import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.stereotype.Service +import org.springframework.stereotype.Component -@Service +@Component class UserDetailsServiceImpl( - private val applicationConfig: ApplicationConfig, + private val actorRepository: ActorRepository, private val userDetailRepository: UserDetailRepository, + private val applicationConfig: ApplicationConfig, private val transaction: Transaction, - private val actorRepository: ActorRepository -) : - UserDetailsService { +) : UserDetailsService { override fun loadUserByUsername(username: String?): UserDetails = runBlocking { if (username == null) { - throw UsernameNotFoundException("$username not found") + throw UsernameNotFoundException("Username not found") } transaction.transaction { - val findById = - actorRepository.findByNameAndDomain(username, applicationConfig.url.host) - ?: throw UserNotFoundException.withNameAndDomain(username, applicationConfig.url.host) - - val userDetails = userDetailRepository.findByActorId(findById.id) - ?: throw UsernameNotFoundException("${findById.id} not found.") - UserDetailsImpl( - id = findById.id, - username = findById.name, - password = userDetails.password, - enabled = true, - accountNonExpired = true, - credentialsNonExpired = true, - accountNonLocked = true, - authorities = mutableListOf() + val actor = actorRepository.findByNameAndDomain(username, applicationConfig.url.host) + ?: throw UsernameNotFoundException("$username not found") + val userDetail = userDetailRepository.findByActorId(actor.id.id) + ?: throw UsernameNotFoundException("${actor.id.id} not found") + HideoutUserDetails( + authorities = HashSet(), + password = userDetail.password.password, + actor.name.name, + userDetailsId = userDetail.id.id ) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt index 5951928c..98252d8e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt @@ -16,12 +16,11 @@ package dev.usbharu.hideout.core.interfaces.api.auth -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.CaptchaConfig -import dev.usbharu.hideout.core.service.auth.AuthApiService -import dev.usbharu.hideout.core.service.auth.RegisterAccountDto +import dev.usbharu.hideout.core.application.actor.RegisterLocalActor +import dev.usbharu.hideout.core.application.actor.RegisterLocalActorApplicationService +import dev.usbharu.hideout.core.infrastructure.springframework.SpringMvcCommandExecutorFactory +import jakarta.servlet.http.HttpServletRequest import org.springframework.stereotype.Controller -import org.springframework.ui.Model import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.ModelAttribute @@ -29,27 +28,21 @@ import org.springframework.web.bind.annotation.PostMapping @Controller class AuthController( - private val authApiService: AuthApiService, - private val captchaConfig: CaptchaConfig, - private val applicationConfig: ApplicationConfig + private val registerLocalActorApplicationService: RegisterLocalActorApplicationService, + private val springMvcCommandExecutorFactory: SpringMvcCommandExecutorFactory, ) { @GetMapping("/auth/sign_up") - fun signUp(model: Model): String { - model.addAttribute("siteKey", captchaConfig.reCaptchaSiteKey) - model.addAttribute("applicationConfig", applicationConfig) - return "sign_up" - } + @Suppress("FunctionOnlyReturningConstant") + fun signUp(): String = "sign_up" @PostMapping("/auth/sign_up") - suspend fun signUp(@Validated @ModelAttribute signUpForm: SignUpForm): String { - val registerAccount = authApiService.registerAccount( - RegisterAccountDto( - signUpForm.username, - signUpForm.password, - signUpForm.recaptchaResponse - ) + suspend fun signUp(@Validated @ModelAttribute signUpForm: SignUpForm, request: HttpServletRequest): String { + val registerLocalActor = RegisterLocalActor(signUpForm.username, signUpForm.password) + val uri = registerLocalActorApplicationService.execute( + registerLocalActor, + springMvcCommandExecutorFactory.getCommandExecutor() ) - - return "redirect:" + registerAccount.url + request.login(signUpForm.username, signUpForm.password) + return "redirect:$uri" } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt index 5c032c5a..d70eb9c2 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt @@ -3,5 +3,5 @@ package dev.usbharu.hideout.core.interfaces.api.auth data class SignUpForm( val username: String, val password: String, - val recaptchaResponse: String +// val recaptchaResponse: String ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/media/LocalFileController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/media/LocalFileController.kt index 3fd2dbe2..d41762c0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/media/LocalFileController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/media/LocalFileController.kt @@ -16,43 +16,21 @@ package dev.usbharu.hideout.core.interfaces.api.media -import dev.usbharu.hideout.application.config.LocalStorageConfig -import dev.usbharu.hideout.core.service.media.FileTypeDeterminationService import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.core.io.ClassPathResource -import org.springframework.core.io.PathResource import org.springframework.core.io.Resource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable -import java.nio.file.Path -import kotlin.io.path.name @Controller @ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) -class LocalFileController( - localStorageConfig: LocalStorageConfig, - private val fileTypeDeterminationService: FileTypeDeterminationService -) { - - private val savePath = Path.of(localStorageConfig.path).toAbsolutePath() +interface LocalFileController { @GetMapping("/files/{id}") - fun files(@PathVariable("id") id: String): ResponseEntity { - val name = Path.of(id).name - val path = savePath.resolve(name) - - val mimeType = fileTypeDeterminationService.fileType(path, name) - val pathResource = PathResource(path) - - return ResponseEntity - .ok() - .contentType(MediaType(mimeType.type, mimeType.subtype)) - .contentLength(pathResource.contentLength()) - .body(pathResource) - } + fun files(@PathVariable("id") id: String): ResponseEntity @GetMapping("/users/{user}/icon.jpg", "/users/{user}/header.jpg") fun icons(): ResponseEntity { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/ExposedFilterQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/ExposedFilterQueryService.kt deleted file mode 100644 index 22defc4d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/ExposedFilterQueryService.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.query.model - -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.infrastructure.exposedrepository.FilterKeywords -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Filters -import dev.usbharu.hideout.core.infrastructure.exposedrepository.toFilter -import dev.usbharu.hideout.core.infrastructure.exposedrepository.toFilterKeyword -import org.jetbrains.exposed.sql.Query -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.springframework.stereotype.Repository - -@Repository -class ExposedFilterQueryService : FilterQueryService { - override suspend fun findByUserIdAndType(userId: Long, types: List): List { - return Filters - .rightJoin(FilterKeywords) - .selectAll() - .where { Filters.userId eq userId } - .toFilterQueryModel() - } - - override suspend fun findByUserId(userId: Long): List { - return Filters - .rightJoin(FilterKeywords) - .selectAll() - .where { Filters.userId eq userId } - .toFilterQueryModel() - } - - override suspend fun findByUserIdAndId(userId: Long, id: Long): FilterQueryModel? { - return Filters - .leftJoin(FilterKeywords) - .selectAll() - .where { Filters.userId eq userId and (Filters.id eq id) } - .toFilterQueryModel() - .firstOrNull() - } - - override suspend fun findByUserIdAndKeywordId(userId: Long, keywordId: Long): FilterQueryModel? { - return Filters - .leftJoin(FilterKeywords) - .selectAll() - .where { Filters.userId eq userId and (FilterKeywords.id eq keywordId) } - .toFilterQueryModel() - .firstOrNull() - } - - private fun Query.toFilterQueryModel(): List { - return this - .groupBy { it[Filters.id] } - .map { it.value } - .map { - FilterQueryModel.of( - it.first().toFilter(), - it.map { resultRow -> resultRow.toFilterKeyword() } - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryModel.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryModel.kt deleted file mode 100644 index 767649c3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryModel.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.query.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.FilterType -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword - -data class FilterQueryModel( - val id: Long, - val userId: Long, - val name: String, - val context: List, - val filterAction: FilterAction, - val keywords: List -) { - companion object { - @Suppress("FunctionMinLength") - fun of(filter: Filter, keywords: List): FilterQueryModel = FilterQueryModel( - id = filter.id, - userId = filter.userId, - name = filter.name, - context = filter.context, - filterAction = filter.filterAction, - keywords = keywords - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryService.kt deleted file mode 100644 index a0ad4f93..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/model/FilterQueryService.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.query.model - -import dev.usbharu.hideout.core.domain.model.filter.FilterType - -interface FilterQueryService { - suspend fun findByUserIdAndType(userId: Long, types: List): List - suspend fun findByUserId(userId: Long): List - suspend fun findByUserIdAndId(userId: Long, id: Long): FilterQueryModel? - suspend fun findByUserIdAndKeywordId(userId: Long, keywordId: Long): FilterQueryModel? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt deleted file mode 100644 index 130ef8a8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.service.auth - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface AuthApiService { - suspend fun registerAccount(registerAccountDto: RegisterAccountDto): Actor -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt deleted file mode 100644 index b1a2c6ed..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.service.auth - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.CaptchaConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.service.user.UserCreateDto -import dev.usbharu.hideout.core.service.user.UserService -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class AuthApiServiceImpl( - private val httpClient: HttpClient, - private val captchaConfig: CaptchaConfig, - private val objectMapper: ObjectMapper, - private val userService: UserService, -) : - AuthApiService { - override suspend fun registerAccount(registerAccountDto: RegisterAccountDto): Actor { - if (captchaConfig.reCaptchaSecretKey != null && captchaConfig.reCaptchaSiteKey != null) { - val get = - httpClient.get( - "https://www.google.com/recaptcha/api/siteverify?secret=" + - captchaConfig.reCaptchaSecretKey + "&response=" + registerAccountDto.recaptchaResponse - ) - val recaptchaResult = objectMapper.readValue(get.bodyAsText()) - logger.debug("reCAPTCHA: {}", recaptchaResult) - require(recaptchaResult.success) - require(!(recaptchaResult.score < 0.5)) - } - - val createLocalUser = userService.createLocalUser( - UserCreateDto( - registerAccountDto.username, - registerAccountDto.username, - "", - registerAccountDto.password - ) - ) - - return createLocalUser - } - - companion object { - private val logger = LoggerFactory.getLogger(AuthApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RecaptchaResult.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RecaptchaResult.kt deleted file mode 100644 index 956fbcd9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RecaptchaResult.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.service.auth - -data class RecaptchaResult( - val success: Boolean, - val challenge_ts: String, - val hostname: String, - val score: Float, - val action: String -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RegisterAccountDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RegisterAccountDto.kt deleted file mode 100644 index c44c291c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/RegisterAccountDto.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.service.auth - -data class RegisterAccountDto( - val username: String, - val password: String, - val recaptchaResponse: String, -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterResult.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterResult.kt deleted file mode 100644 index d87e6fea..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/FilterResult.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.query.model.FilterQueryModel - -data class FilterResult( - val filter: FilterQueryModel, - val keyword: String, -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt deleted file mode 100644 index 8bba0722..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.query.model.FilterQueryModel - -interface MuteProcessService { - suspend fun processMute(post: Post, context: List, filters: List): FilterResult? - suspend fun processMutes( - posts: List, - context: List, - filters: List - ): Map -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt deleted file mode 100644 index 491b3985..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterMode.* -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class MuteProcessServiceImpl : MuteProcessService { - override suspend fun processMute( - post: Post, - context: List, - filters: List - ): FilterResult? { - val preprocess = preprocess(context, filters) - - return processMute(post, preprocess) - } - - private suspend fun processMute( - post: Post, - preprocess: List - ): FilterResult? { - logger.trace("process mute post: {}", post) - if (post.overview != null) { - val processMute = processMute(post.overview, preprocess) - - if (processMute != null) { - return processMute - } - } - - val processMute = processMute(post.text, preprocess) - - if (processMute != null) { - return processMute - } - - return null - } - - override suspend fun processMutes( - posts: List, - context: List, - filters: List - ): Map { - val preprocess = preprocess(context, filters) - - return posts.mapNotNull { it to (processMute(it, preprocess) ?: return@mapNotNull null) }.toMap() - } - - private suspend fun processMute(string: String, filters: List): FilterResult? { - for (filter in filters) { - val matchEntire = filter.regex.find(string) - - if (matchEntire != null) { - return FilterResult(filter.filter, matchEntire.value) - } - } - - return null - } - - private fun preprocess(context: List, filters: List): List { - val filterQueryModelList = filters - .filter { it.context.any(context::contains) } - .map { - PreProcessedFilter( - it, - precompileRegex(it) - ) - } - - return filterQueryModelList - } - - private fun precompileRegex(filter: FilterQueryModel): Regex { - logger.trace("precompile regex. filter: {}", filter) - - val regexList = mutableListOf() - - val noneRegexStrings = mutableListOf() - val wholeRegexStrings = mutableListOf() - - for (keyword in filter.keywords) { - when (keyword.mode) { - WHOLE_WORD -> wholeRegexStrings.add(keyword.keyword) - REGEX -> regexList.add(Regex(keyword.keyword)) - NONE -> noneRegexStrings.add(keyword.keyword) - } - } - - val noneRegex = noneRegexStrings.joinToString("|", "(", ")") - val wholeRegex = wholeRegexStrings.joinToString("|", "\\b(", ")\\b") - - val regex = if (noneRegexStrings.isNotEmpty() && wholeRegexStrings.isNotEmpty()) { - Regex("$noneRegex|$wholeRegex") - } else if (noneRegexStrings.isNotEmpty()) { - noneRegex.toRegex() - } else if (wholeRegexStrings.isNotEmpty()) { - wholeRegex.toRegex() - } else { - null - } - - if (regex != null) { - regexList.add(regex) - } - - val pattern = regexList.joinToString(")|(", "(", ")") - logger.trace("precompiled regex {}", pattern) - - return Regex(pattern) - } - - data class PreProcessedFilter(val filter: FilterQueryModel, val regex: Regex) - - companion object { - private val logger = LoggerFactory.getLogger(MuteProcessServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteService.kt deleted file mode 100644 index 63e1ac83..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteService.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterAction -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.query.model.FilterQueryModel - -interface MuteService { - suspend fun createFilter( - title: String, - context: List, - action: FilterAction, - keywords: List, - loginUser: Long - ): FilterQueryModel - - suspend fun getFilters(userId: Long, types: List = emptyList()): List -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImpl.kt deleted file mode 100644 index 27872d7d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImpl.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.service.filter - -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.FilterRepository -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import dev.usbharu.hideout.core.query.model.FilterQueryService -import org.springframework.stereotype.Service - -@Service -class MuteServiceImpl( - private val filterRepository: FilterRepository, - private val filterKeywordRepository: FilterKeywordRepository, - private val filterQueryService: FilterQueryService -) : MuteService { - override suspend fun createFilter( - title: String, - context: List, - action: FilterAction, - keywords: List, - loginUser: Long - ): FilterQueryModel { - val filter = Filter( - filterRepository.generateId(), - loginUser, - title, - context, - action - ) - - val filterKeywordList = keywords.map { - dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword( - filterKeywordRepository.generateId(), - filter.id, - it.keyword, - it.mode - ) - } - - val savedFilter = filterRepository.save(filter) - - filterKeywordRepository.saveAll(filterKeywordList) - return FilterQueryModel.of(savedFilter, filterKeywordList) - } - - override suspend fun getFilters(userId: Long, types: List): List = - filterQueryService.findByUserIdAndType(userId, types) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt deleted file mode 100644 index 2e85a805..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.service.follow - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -data class SendFollowDto(val actorId: Actor, val followTargetActorId: Actor) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceCreateDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceCreateDto.kt deleted file mode 100644 index f4d04b30..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceCreateDto.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.service.instance - -data class InstanceCreateDto( - val name: String?, - val description: String?, - val url: String, - val iconUrl: String, - val sharedInbox: String?, - val software: String?, - val version: String?, -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceService.kt deleted file mode 100644 index a9a84f3d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/instance/InstanceService.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.service.instance - -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.core.domain.model.instance.Instance -import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository -import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo -import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo2_0 -import dev.usbharu.hideout.core.service.resource.ResourceResolveService -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.net.URL -import java.time.Instant - -interface InstanceService { - suspend fun fetchInstance(url: String, sharedInbox: String? = null): Instance - suspend fun createNewInstance(instanceCreateDto: InstanceCreateDto): Instance -} - -@Service -class InstanceServiceImpl( - private val instanceRepository: InstanceRepository, - private val resourceResolveService: ResourceResolveService, - @Qualifier("activitypub") private val objectMapper: ObjectMapper, -) : InstanceService { - override suspend fun fetchInstance(url: String, sharedInbox: String?): Instance { - val u = URL(url) - val resolveInstanceUrl = u.protocol + "://" + u.host - - val instance = instanceRepository.findByUrl(resolveInstanceUrl) - - if (instance != null) { - return instance - } - - logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl) - @Suppress("TooGenericExceptionCaught") - try { - val nodeinfoJson = resourceResolveService.resolve("$resolveInstanceUrl/.well-known/nodeinfo").bodyAsText() - val nodeinfo = objectMapper.readValue(nodeinfoJson, Nodeinfo::class.java) - val nodeinfoPathMap = nodeinfo.links.associate { it.rel to it.href } - - for ((key, value) in nodeinfoPathMap) { - when (key) { - "http://nodeinfo.diaspora.software/ns/schema/2.0", - "http://nodeinfo.diaspora.software/ns/schema/2.1", - -> { - val nodeinfo20 = objectMapper.readValue( - resourceResolveService.resolve(value!!).bodyAsText(), - Nodeinfo2_0::class.java - ) - - val instanceCreateDto = InstanceCreateDto( - name = nodeinfo20.metadata?.nodeName, - description = nodeinfo20.metadata?.nodeDescription, - url = resolveInstanceUrl, - iconUrl = "$resolveInstanceUrl/favicon.ico", - sharedInbox = sharedInbox, - software = nodeinfo20.software?.name, - version = nodeinfo20.software?.version - ) - return createNewInstance(instanceCreateDto) - } - - else -> { - throw IllegalStateException("Unknown nodeinfo versions: $key url: $value") - } - } - } - } catch (e: Exception) { - logger.warn("FAILED Fetch Instance", e) - } - return createNewInstance( - InstanceCreateDto( - name = null, - description = null, - url = resolveInstanceUrl, - iconUrl = "$resolveInstanceUrl/favicon.ico", - sharedInbox = null, - software = null, - version = null - ) - ) - } - - override suspend fun createNewInstance(instanceCreateDto: InstanceCreateDto): Instance { - val instance = Instance( - id = instanceRepository.generateId(), - name = instanceCreateDto.name ?: instanceCreateDto.url, - description = instanceCreateDto.description.orEmpty(), - url = instanceCreateDto.url, - iconUrl = instanceCreateDto.iconUrl, - sharedInbox = instanceCreateDto.sharedInbox, - software = instanceCreateDto.software ?: "unknown", - version = instanceCreateDto.version ?: "unknown", - isBlocked = false, - isMuted = false, - moderationNote = "", - createdAt = Instant.now() - ) - instanceRepository.save(instance) - return instance - } - - companion object { - private val logger = LoggerFactory.getLogger(InstanceServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationService.kt deleted file mode 100644 index b4aa4c57..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationService.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.service.media - -import org.apache.tika.Tika -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component -import java.nio.file.Path - -@Component -class ApatcheTikaFileTypeDeterminationService : FileTypeDeterminationService { - override fun fileType( - byteArray: ByteArray, - filename: String, - contentType: String? - ): MimeType { - logger.info("START Detect file type name: {}", filename) - - val tika = Tika() - - val detect = try { - tika.detect(byteArray, filename) - } catch (e: IllegalStateException) { - logger.warn("FAILED Detect file type", e) - "application/octet-stream" - } - - val type = detect.substringBefore("/") - val fileType = when (type) { - "image" -> { - FileType.Image - } - - "video" -> { - FileType.Video - } - - "audio" -> { - FileType.Audio - } - - else -> { - FileType.Unknown - } - } - val mimeType = MimeType(type, detect.substringAfter("/"), fileType) - - logger.info("SUCCESS Detect file type name: {},MimeType: {}", filename, mimeType) - return mimeType - } - - override fun fileType(path: Path, filename: String): MimeType { - logger.info("START Detect file type name: {}", filename) - - val tika = Tika() - - val detect = try { - tika.detect(path) - } catch (e: IllegalStateException) { - logger.warn("FAILED Detect file type", e) - "application/octet-stream" - } - - val type = detect.substringBefore("/") - val fileType = when (type) { - "image" -> { - FileType.Image - } - - "video" -> { - FileType.Video - } - - "audio" -> { - FileType.Audio - } - - else -> { - FileType.Unknown - } - } - val mimeType = MimeType(type, detect.substringAfter("/"), fileType) - - logger.info("SUCCESS Detect file type name: {},MimeType: {}", filename, mimeType) - return mimeType - } - - companion object { - private val logger = LoggerFactory.getLogger(ApatcheTikaFileTypeDeterminationService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt deleted file mode 100644 index 7ca382f6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.service.media - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.LocalStorageConfig -import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Service -import java.nio.file.Path -import java.nio.file.StandardOpenOption -import kotlin.io.path.copyTo -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteIfExists -import kotlin.io.path.outputStream - -/** - * ローカルファイルシステムにメディアを保存します - * - * @constructor - * ApplicationConfigとLocalStorageConfigをもとに作成 - * - * @param applicationConfig ApplicationConfig - * @param localStorageConfig LocalStorageConfig - */ -@Service -@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) -class LocalFileSystemMediaDataStore( - applicationConfig: ApplicationConfig, - localStorageConfig: LocalStorageConfig -) : MediaDataStore { - - private val savePath: Path = Path.of(localStorageConfig.path).toAbsolutePath() - - private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/" - - init { - savePath.createDirectories() - } - - @Suppress("NestedBlockDepth") - override suspend fun save(dataMediaSave: MediaSave): SavedMedia { - val fileSavePath = buildSavePath(savePath, dataMediaSave.name) - val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name) - - dataMediaSave.thumbnailInputStream?.inputStream()?.use { - it.buffered().use { bufferedInputStream -> - thumbnailSavePath.outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) - .use { outputStream -> - outputStream.buffered().use { - bufferedInputStream.transferTo(it) - } - } - } - } - - dataMediaSave.fileInputStream.inputStream().use { - it.buffered().use { bufferedInputStream -> - fileSavePath.outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) - .use { outputStream -> outputStream.buffered().use { bufferedInputStream.transferTo(it) } } - } - } - - return SuccessSavedMedia( - dataMediaSave.name, - publicUrl + dataMediaSave.name, - publicUrl + "thumbnail-" + dataMediaSave.name - ) - } - - override suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia { - logger.info("START Media upload. {}", dataSaveRequest.name) - val fileSavePath = buildSavePath(savePath, dataSaveRequest.name) - val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataSaveRequest.name) - - val fileSavePathString = fileSavePath.toAbsolutePath().toString() - logger.info("MEDIA save. path: {}", fileSavePathString) - - @Suppress("TooGenericExceptionCaught") - try { - dataSaveRequest.filePath.copyTo(fileSavePath) - dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath) - } catch (e: Exception) { - logger.warn("FAILED to Save the media.", e) - return FaildSavedMedia("FAILED to Save the media.", "Failed copy to path: $fileSavePathString", e) - } - - logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) - return SuccessSavedMedia( - dataSaveRequest.name, - publicUrl + dataSaveRequest.name, - publicUrl + "thumbnail-" + dataSaveRequest.name - ) - } - - /** - * メディアを削除します。サムネイルも削除されます。 - * - * @param id 削除するメディアのid [SuccessSavedMedia.name]を指定します。 - */ - override suspend fun delete(id: String) { - logger.info("START Media delete. id: {}", id) - @Suppress("TooGenericExceptionCaught") - try { - buildSavePath(savePath, id).deleteIfExists() - buildSavePath(savePath, "thumbnail-$id").deleteIfExists() - } catch (e: Exception) { - logger.warn("FAILED Media delete. id: {}", id, e) - } - } - - private fun buildSavePath(savePath: Path, name: String): Path = savePath.resolve(name) - - companion object { - private val logger = LoggerFactory.getLogger(LocalFileSystemMediaDataStore::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashService.kt deleted file mode 100644 index 13fd1eee..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.service.media - -import java.awt.image.BufferedImage - -interface MediaBlurhashService { - fun generateBlurhash(bufferedImage: BufferedImage): String -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashServiceImpl.kt deleted file mode 100644 index 963554a3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaBlurhashServiceImpl.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.service.media - -import io.trbl.blurhash.BlurHash -import org.springframework.stereotype.Service -import java.awt.image.BufferedImage - -@Service -class MediaBlurhashServiceImpl : MediaBlurhashService { - override fun generateBlurhash(bufferedImage: BufferedImage): String = BlurHash.encode(bufferedImage) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt deleted file mode 100644 index 79a30159..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.service.media - -/** - * メディアを保存するインタフェース - * - */ -interface MediaDataStore { - /** - * InputStreamを使用してメディアを保存します - * - * @param dataMediaSave FileとThumbnailのinputStream - * @return 保存されたメディア - */ - suspend fun save(dataMediaSave: MediaSave): SavedMedia - - /** - * 一時ファイルのパスを使用してメディアを保存します - * - * @param dataSaveRequest FileとThumbnailのパス - * @return 保存されたメディア - */ - suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia - - /** - * メディアを削除します - * 実装はサムネイル、メタデータなども削除するべきです。 - * - * @param id 削除するメディアのid 通常は[SuccessSavedMedia.name]を指定します。 - */ - suspend fun delete(id: String) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaFileRenameService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaFileRenameService.kt deleted file mode 100644 index b130cb75..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaFileRenameService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.service.media - -interface MediaFileRenameService { - /** - * メディアをリネームします - * - * @param uploadName アップロードされた時点でのファイル名 - * @param uploadMimeType アップロードされた時点でのMimeType - * @param processedName 処理後のファイル名 - * @param processedMimeType 処理後のMimeType - * @return リネーム後のファイル名 - */ - fun rename(uploadName: String, uploadMimeType: MimeType, processedName: String, processedMimeType: MimeType): String -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSave.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSave.kt deleted file mode 100644 index f8754288..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaSave.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.service.media - -data class MediaSave( - val name: String, - val prefix: String, - val fileInputStream: ByteArray, - val thumbnailInputStream: ByteArray? -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as MediaSave - - if (name != other.name) return false - if (prefix != other.prefix) return false - if (!fileInputStream.contentEquals(other.fileInputStream)) return false - if (thumbnailInputStream != null) { - if (other.thumbnailInputStream == null) return false - if (!thumbnailInputStream.contentEquals(other.thumbnailInputStream)) return false - } else if (other.thumbnailInputStream != null) return false - - return true - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + prefix.hashCode() - result = 31 * result + fileInputStream.contentHashCode() - result = 31 * result + (thumbnailInputStream?.contentHashCode() ?: 0) - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt deleted file mode 100644 index d938d5df..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.service.media - -import dev.usbharu.hideout.core.domain.exception.media.MediaSaveException -import dev.usbharu.hideout.core.domain.exception.media.UnsupportedMediaException -import dev.usbharu.hideout.core.domain.model.media.Media -import dev.usbharu.hideout.core.domain.model.media.MediaRepository -import dev.usbharu.hideout.core.service.media.converter.MediaProcessService -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import dev.usbharu.hideout.util.withDelete -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.nio.file.Files -import javax.imageio.ImageIO -import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia - -@Service -@Suppress("TooGenericExceptionCaught") -class MediaServiceImpl( - private val mediaDataStore: MediaDataStore, - private val fileTypeDeterminationService: FileTypeDeterminationService, - private val mediaBlurhashService: MediaBlurhashService, - private val mediaRepository: MediaRepository, - private val mediaProcessServices: List, - private val remoteMediaDownloadService: RemoteMediaDownloadService, - private val renameService: MediaFileRenameService -) : MediaService { - @Suppress("LongMethod", "NestedBlockDepth") - override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia { - val fileName = mediaRequest.file.name - logger.info( - "Media upload. filename:$fileName " + - "contentType:${mediaRequest.file.contentType}" - ) - - val tempFile = Files.createTempFile("hideout-tmp-file", ".tmp") - - tempFile.withDelete().use { - Files.newOutputStream(tempFile).use { outputStream -> - mediaRequest.file.inputStream.use { - it.transferTo(outputStream) - } - } - val mimeType = fileTypeDeterminationService.fileType(tempFile, fileName) - - val process = findMediaProcessor(mimeType).process( - mimeType, - fileName, - tempFile, - null - ) - - val dataMediaSave = MediaSaveRequest( - renameService.rename( - mediaRequest.file.name, - mimeType, - process.filePath.fileName.toString(), - process.fileMimeType - ), - "", - process.filePath, - process.thumbnailPath - ) - dataMediaSave.filePath.withDelete().use { - dataMediaSave.thumbnailPath.withDelete().use { - val save = try { - mediaDataStore.save(dataMediaSave) - } catch (e: Exception) { - logger.warn("Failed to save the media", e) - throw MediaSaveException("Failed to save the media.", e) - } - if (save.success.not()) { - save as FaildSavedMedia - logger.warn("Failed to save the media. reason: ${save.reason}") - logger.warn(save.description, save.trace) - throw MediaSaveException("Failed to save the media.") - } - save as SuccessSavedMedia - val blurHash = generateBlurhash(process) - return mediaRepository.save( - EntityMedia( - id = mediaRepository.generateId(), - name = fileName, - url = save.url, - remoteUrl = null, - thumbnailUrl = save.thumbnailUrl, - type = process.fileMimeType.fileType, - mimeType = process.fileMimeType, - blurHash = blurHash, - description = mediaRequest.description - ) - ) - } - } - } - } - - // TODO: 仮の処理として保存したように動かす - @Suppress("LongMethod", "NestedBlockDepth") - override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { - logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") - - val findByRemoteUrl = mediaRepository.findByRemoteUrl(remoteMedia.url) - if (findByRemoteUrl != null) { - logger.warn("DUPLICATED Remote media is duplicated. url: {}", remoteMedia.url) - return findByRemoteUrl - } - - remoteMediaDownloadService.download(remoteMedia.url).withDelete().use { - val mimeType = fileTypeDeterminationService.fileType(it.path, remoteMedia.name) - - val process = findMediaProcessor(mimeType).process(mimeType, remoteMedia.name, it.path, null) - - val mediaSaveRequest = MediaSaveRequest( - renameService.rename( - remoteMedia.name, - mimeType, - process.filePath.fileName.toString(), - process.fileMimeType - ), - "", - process.filePath, - process.thumbnailPath - ) - - mediaSaveRequest.filePath.withDelete().use { - mediaSaveRequest.filePath.withDelete().use { - val save = try { - mediaDataStore.save(mediaSaveRequest) - } catch (e: Exception) { - logger.warn("Failed to save the media", e) - throw MediaSaveException("Failed to save the media.", e) - } - - if (save is FaildSavedMedia) { - logger.warn("Failed to save the media. reason: ${save.reason}") - logger.warn(save.description, save.trace) - throw MediaSaveException("Failed to save the media.") - } - save as SuccessSavedMedia - val blurhash = generateBlurhash(process) - return mediaRepository.save( - EntityMedia( - id = mediaRepository.generateId(), - name = remoteMedia.name, - url = save.url, - remoteUrl = remoteMedia.url, - thumbnailUrl = save.thumbnailUrl, - type = process.fileMimeType.fileType, - mimeType = process.fileMimeType, - blurHash = blurhash - ) - ) - } - } - } - } - - private fun findMediaProcessor(mimeType: MimeType): MediaProcessService { - try { - return mediaProcessServices.first { - try { - it.isSupport(mimeType) - } catch (_: Exception) { - false - } - } - } catch (_: NoSuchElementException) { - throw UnsupportedMediaException("MediaType: $mimeType isn't supported.") - } - } - - private fun generateBlurhash(process: ProcessedMediaPath): String { - val path = if (process.thumbnailPath != null && process.thumbnailMimeType != null) { - process.thumbnailPath - } else { - process.filePath - } - val mimeType = if (process.thumbnailPath != null && process.thumbnailMimeType != null) { - process.thumbnailMimeType - } else { - process.fileMimeType - } - - val imageReadersByMIMEType = ImageIO.getImageReadersByMIMEType(mimeType.type + "/" + mimeType.subtype) - for (imageReader in imageReadersByMIMEType) { - try { - val bufferedImage = ImageIO.createImageInputStream(path.toFile()).use { - imageReader.input = it - imageReader.read(0) - } - return mediaBlurhashService.generateBlurhash(bufferedImage) - } catch (e: Exception) { - logger.warn("Failed to read thumbnail", e) - } - } - return "" - } - - companion object { - private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedFile.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedFile.kt deleted file mode 100644 index d02f3be1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedFile.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.service.media - -data class ProcessedFile( - val byteArray: ByteArray, - val extension: String -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ProcessedFile - - if (!byteArray.contentEquals(other.byteArray)) return false - if (extension != other.extension) return false - - return true - } - - override fun hashCode(): Int { - var result = byteArray.contentHashCode() - result = 31 * result + extension.hashCode() - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMediaPath.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMediaPath.kt deleted file mode 100644 index 61023d3e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ProcessedMediaPath.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.service.media - -import java.nio.file.Path - -data class ProcessedMediaPath( - val filePath: Path, - val thumbnailPath: Path?, - val fileMimeType: MimeType, - val thumbnailMimeType: MimeType? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMedia.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMedia.kt deleted file mode 100644 index 0a2c21d9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMedia.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.service.media - -data class RemoteMedia( - val name: String, - val url: String, - val mediaType: String, - val description: String? = null -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt deleted file mode 100644 index 26843782..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.service.media - -import dev.usbharu.hideout.application.config.MediaConfig -import dev.usbharu.hideout.core.domain.exception.media.RemoteMediaFileSizeException -import dev.usbharu.hideout.core.service.resource.KtorResourceResolveService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.outputStream - -@Service -class RemoteMediaDownloadServiceImpl( - private val resourceResolveService: KtorResourceResolveService, - private val mediaConfig: MediaConfig -) : - RemoteMediaDownloadService { - override suspend fun download(url: String): Path { - logger.info("START Download remote file. url: {}", url) - val httpResponse = resourceResolveService.resolve(url).body() - val createTempFile = Files.createTempFile("hideout-remote-download", ".tmp") - - logger.debug("Save to {} url: {} ", createTempFile, url) - - httpResponse.use { inputStream -> - createTempFile.outputStream().use { - inputStream.transferTo(it) - } - } - - val contentLength = createTempFile.toFile().length() - if (contentLength >= mediaConfig.remoteMediaFileSizeLimit) { - throw RemoteMediaFileSizeException( - "File size is too large. $contentLength >= ${mediaConfig.remoteMediaFileSizeLimit}" - ) - } - - logger.info("SUCCESS Download remote file. url: {}", url) - return createTempFile - } - - companion object { - private val logger = LoggerFactory.getLogger(RemoteMediaDownloadServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt deleted file mode 100644 index 8fb8ee8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.service.media - -import dev.usbharu.hideout.application.config.S3StorageConfig -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.withContext -import org.slf4j.LoggerFactory -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Service -import software.amazon.awssdk.core.sync.RequestBody -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest -import software.amazon.awssdk.services.s3.model.GetUrlRequest -import software.amazon.awssdk.services.s3.model.PutObjectRequest - -@Service -@ConditionalOnProperty("hideout.storage.type", havingValue = "s3") -class S3MediaDataStore(private val s3Client: S3Client, private val s3StorageConfig: S3StorageConfig) : MediaDataStore { - override suspend fun save(dataMediaSave: MediaSave): SavedMedia { - val fileUploadRequest = PutObjectRequest.builder() - .bucket(s3StorageConfig.bucket) - .key(dataMediaSave.name) - .build() - - val thumbnailKey = "thumbnail-${dataMediaSave.name}" - val thumbnailUploadRequest = PutObjectRequest.builder() - .bucket(s3StorageConfig.bucket) - .key(thumbnailKey) - .build() - - withContext(Dispatchers.IO) { - awaitAll( - async { - if (dataMediaSave.thumbnailInputStream != null) { - s3Client.putObject( - thumbnailUploadRequest, - RequestBody.fromBytes(dataMediaSave.thumbnailInputStream) - ) - s3Client.utilities() - .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(thumbnailKey).build()) - } else { - null - } - }, - async { - s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) - s3Client.utilities() - .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(dataMediaSave.name).build()) - } - ) - } - return SuccessSavedMedia( - name = dataMediaSave.name, - url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataMediaSave.name}", - thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey" - ) - } - - override suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia { - logger.info("MEDIA upload. {}", dataSaveRequest.name) - - val fileUploadRequest = PutObjectRequest.builder() - .bucket(s3StorageConfig.bucket) - .key(dataSaveRequest.name) - .build() - - logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, dataSaveRequest.name) - - val thumbnailKey = "thumbnail-${dataSaveRequest.name}" - val thumbnailUploadRequest = PutObjectRequest.builder() - .bucket(s3StorageConfig.bucket) - .key(thumbnailKey) - .build() - - logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, thumbnailKey) - - withContext(Dispatchers.IO) { - awaitAll( - async { - if (dataSaveRequest.thumbnailPath != null) { - s3Client.putObject( - thumbnailUploadRequest, - RequestBody.fromFile(dataSaveRequest.thumbnailPath) - ) - } else { - null - } - }, - async { - s3Client.putObject(fileUploadRequest, RequestBody.fromFile(dataSaveRequest.filePath)) - } - ) - } - val successSavedMedia = SuccessSavedMedia( - name = dataSaveRequest.name, - url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataSaveRequest.name}", - thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey" - ) - - logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) - logger.debug( - "name: {} url: {} thumbnail url: {}", - successSavedMedia.name, - successSavedMedia.url, - successSavedMedia.thumbnailUrl - ) - - return successSavedMedia - } - - override suspend fun delete(id: String) { - val fileDeleteRequest = DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key(id).build() - val thumbnailDeleteRequest = - DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key("thumbnail-$id").build() - s3Client.deleteObject(fileDeleteRequest) - s3Client.deleteObject(thumbnailDeleteRequest) - } - - companion object { - private val logger = LoggerFactory.getLogger(S3MediaDataStore::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt deleted file mode 100644 index 14413c25..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/SavedMedia.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.service.media - -sealed class SavedMedia(val success: Boolean) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as SavedMedia - - return success == other.success - } - - override fun hashCode(): Int = success.hashCode() -} - -class SuccessSavedMedia( - val name: String, - val url: String, - val thumbnailUrl: String, -) : - SavedMedia(true) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as SuccessSavedMedia - - if (name != other.name) return false - if (url != other.url) return false - if (thumbnailUrl != other.thumbnailUrl) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + thumbnailUrl.hashCode() - return result - } -} - -class FaildSavedMedia( - val reason: String, - val description: String, - val trace: Throwable? = null -) : SavedMedia(false) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as FaildSavedMedia - - if (reason != other.reason) return false - if (description != other.description) return false - if (trace != other.trace) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + reason.hashCode() - result = 31 * result + description.hashCode() - result = 31 * result + (trace?.hashCode() ?: 0) - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateService.kt deleted file mode 100644 index 71258673..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateService.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.service.media - -import java.io.InputStream - -interface ThumbnailGenerateService { - fun generate(bufferedImage: InputStream, width: Int, height: Int): ProcessedFile? - fun generate(outputStream: ByteArray, width: Int, height: Int): ProcessedFile? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateServiceImpl.kt deleted file mode 100644 index c23fb2a6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/ThumbnailGenerateServiceImpl.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.service.media - -import org.springframework.stereotype.Service -import java.awt.image.BufferedImage -import java.io.ByteArrayOutputStream -import java.io.InputStream -import javax.imageio.ImageIO - -@Service -class ThumbnailGenerateServiceImpl : ThumbnailGenerateService { - override fun generate(bufferedImage: InputStream, width: Int, height: Int): ProcessedFile? { - val image = ImageIO.read(bufferedImage) - return internalGenerate(image) - } - - override fun generate(outputStream: ByteArray, width: Int, height: Int): ProcessedFile? { - val image = ImageIO.read(outputStream.inputStream()) - return internalGenerate(image) - } - - private fun internalGenerate(image: BufferedImage): ProcessedFile { - val byteArrayOutputStream = ByteArrayOutputStream() - ImageIO.write(image, "jpeg", byteArrayOutputStream) - return ProcessedFile(byteArrayOutputStream.toByteArray(), "jpg") - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/UUIDMediaFileRenameService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/UUIDMediaFileRenameService.kt deleted file mode 100644 index 3a9e748f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/UUIDMediaFileRenameService.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.service.media - -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.util.* - -@Qualifier("uuid") -@Service -class UUIDMediaFileRenameService : MediaFileRenameService { - override fun rename( - uploadName: String, - uploadMimeType: MimeType, - processedName: String, - processedMimeType: MimeType - ): String = "${UUID.randomUUID()}.${uploadMimeType.subtype}.${processedMimeType.subtype}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverter.kt deleted file mode 100644 index e7767896..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverter.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.service.media.converter - -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.ProcessedFile -import java.io.InputStream - -interface MediaConverter { - fun isSupport(fileType: FileType): Boolean - fun convert(inputStream: InputStream): ProcessedFile -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRoot.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRoot.kt deleted file mode 100644 index 5b837f7c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRoot.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.service.media.converter - -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.ProcessedFile -import java.io.InputStream - -interface MediaConverterRoot { - suspend fun convert( - fileType: FileType, - contentType: String, - filename: String, - inputStream: InputStream - ): ProcessedFile -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRootImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRootImpl.kt deleted file mode 100644 index 79c44ec3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaConverterRootImpl.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.service.media.converter - -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.ProcessedFile -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.springframework.stereotype.Service -import java.io.InputStream - -@Service -class MediaConverterRootImpl(private val converters: List) : MediaConverterRoot { - override suspend fun convert( - fileType: FileType, - contentType: String, - filename: String, - inputStream: InputStream - ): ProcessedFile { - val convert = converters.find { - it.isSupport(fileType) - }?.convert(inputStream) - if (convert != null) { - return convert - } - return withContext(Dispatchers.IO) { - if (filename.contains('.')) { - ProcessedFile(inputStream.readAllBytes(), filename.substringAfterLast(".")) - } else { - ProcessedFile(inputStream.readAllBytes(), contentType.substringAfterLast("/")) - } - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessService.kt deleted file mode 100644 index 53a11fc7..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessService.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.service.media.converter - -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MimeType -import dev.usbharu.hideout.core.service.media.ProcessedMedia -import dev.usbharu.hideout.core.service.media.ProcessedMediaPath -import java.nio.file.Path - -interface MediaProcessService { - fun isSupport(mimeType: MimeType): Boolean - - suspend fun process( - fileType: FileType, - contentType: String, - fileName: String, - file: ByteArray, - thumbnail: ByteArray? - ): ProcessedMedia - - suspend fun process( - mimeType: MimeType, - fileName: String, - filePath: Path, - thumbnails: Path? - ): ProcessedMediaPath -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt deleted file mode 100644 index bd5f5f0a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/MediaProcessServiceImpl.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.service.media.converter - -import dev.usbharu.hideout.core.domain.exception.media.MediaConvertException -import dev.usbharu.hideout.core.service.media.* -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.nio.file.Path - -@Service -@Suppress("TooGenericExceptionCaught") -class MediaProcessServiceImpl( - private val mediaConverterRoot: MediaConverterRoot, - private val thumbnailGenerateService: ThumbnailGenerateService -) : MediaProcessService { - override fun isSupport(mimeType: MimeType): Boolean = false - - override suspend fun process( - fileType: FileType, - contentType: String, - filename: String, - file: ByteArray, - thumbnail: ByteArray? - ): ProcessedMedia { - val fileInputStream = try { - mediaConverterRoot.convert(fileType, contentType, filename, file.inputStream().buffered()) - } catch (e: Exception) { - logger.warn("Failed convert media.", e) - throw MediaConvertException("Failed convert media.", e) - } - val thumbnailInputStream = try { - thumbnail?.let { mediaConverterRoot.convert(fileType, contentType, filename, it.inputStream().buffered()) } - } catch (e: Exception) { - logger.warn("Failed convert thumbnail media.", e) - null - } - return ProcessedMedia( - fileInputStream, - thumbnailGenerateService.generate( - thumbnailInputStream?.byteArray ?: file, - 2048, - 2048 - ) - ) - } - - override suspend fun process( - mimeType: MimeType, - fileName: String, - filePath: Path, - thumbnails: Path? - ): ProcessedMediaPath { - TODO("Not yet implemented") - } - - companion object { - private val logger = LoggerFactory.getLogger(MediaProcessServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessService.kt deleted file mode 100644 index c41b7da8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessService.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.service.media.converter.image - -import dev.usbharu.hideout.core.domain.exception.media.MediaProcessException -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MimeType -import dev.usbharu.hideout.core.service.media.ProcessedMedia -import dev.usbharu.hideout.core.service.media.ProcessedMediaPath -import dev.usbharu.hideout.core.service.media.converter.MediaProcessService -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.slf4j.MDCContext -import kotlinx.coroutines.withContext -import net.coobird.thumbnailator.Thumbnails -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.awt.Color -import java.awt.image.BufferedImage -import java.nio.file.Files -import java.nio.file.Path -import java.util.* -import javax.imageio.ImageIO -import kotlin.io.path.inputStream -import kotlin.io.path.outputStream - -@Service -@Qualifier("image") -class ImageMediaProcessService(private val imageMediaProcessorConfiguration: ImageMediaProcessorConfiguration?) : - MediaProcessService { - - private val convertType = imageMediaProcessorConfiguration?.convert ?: "jpeg" - - private val supportedTypes = imageMediaProcessorConfiguration?.supportedType ?: listOf("webp", "jpeg", "png") - - private val genThumbnail = imageMediaProcessorConfiguration?.thubnail?.generate ?: true - - private val width = imageMediaProcessorConfiguration?.thubnail?.width ?: 1000 - private val height = imageMediaProcessorConfiguration?.thubnail?.height ?: 1000 - - override fun isSupport(mimeType: MimeType): Boolean { - if (mimeType.type != "image") { - return false - } - return supportedTypes.contains(mimeType.subtype) - } - - override suspend fun process( - fileType: FileType, - contentType: String, - fileName: String, - file: ByteArray, - thumbnail: ByteArray? - ): ProcessedMedia { - TODO("Not yet implemented") - } - - override suspend fun process( - mimeType: MimeType, - fileName: String, - filePath: Path, - thumbnails: Path? - ): ProcessedMediaPath = withContext(Dispatchers.IO + MDCContext()) { - val read = ImageIO.read(filePath.inputStream()) - - val bufferedImage = BufferedImage(read.width, read.height, BufferedImage.TYPE_INT_RGB) - - val graphics = bufferedImage.createGraphics() - - graphics.drawImage(read, 0, 0, Color.BLACK, null) - - val tempFileName = UUID.randomUUID().toString() - val tempFile = Files.createTempFile(tempFileName, "tmp") - - val thumbnailPath = if (genThumbnail) { - val tempThumbnailFile = Files.createTempFile("thumbnail-$tempFileName", ".tmp") - - tempThumbnailFile.outputStream().use { - val write = ImageIO.write( - if (thumbnails != null) { - Thumbnails.of(thumbnails.toFile()) - .size(width, height) - .imageType(BufferedImage.TYPE_INT_RGB) - .asBufferedImage() - } else { - Thumbnails.of(bufferedImage) - .size(width, height) - .imageType(BufferedImage.TYPE_INT_RGB) - .asBufferedImage() - }, - convertType, - it - ) - tempThumbnailFile.takeIf { write } - } - } else { - null - } - - tempFile.outputStream().use { - if (ImageIO.write(bufferedImage, convertType, it).not()) { - logger.warn("Failed to save a temporary file. type: {} ,path: {}", convertType, tempFile) - throw MediaProcessException("Failed to save a temporary file.") - } - } - ProcessedMediaPath( - tempFile, - thumbnailPath, - MimeType("image", convertType, FileType.Image), - MimeType("image", convertType, FileType.Image).takeIf { genThumbnail } - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(ImageMediaProcessService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessorConfiguration.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessorConfiguration.kt deleted file mode 100644 index cd24a7d3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/image/ImageMediaProcessorConfiguration.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.service.media.converter.image - -import org.springframework.boot.context.properties.ConfigurationProperties - -@ConfigurationProperties("hideout.media.image") -data class ImageMediaProcessorConfiguration( - val convert: String?, - val thubnail: ImageMediaProcessorThumbnailConfiguration?, - val supportedType: List?, - -) - -data class ImageMediaProcessorThumbnailConfiguration( - val generate: Boolean, - val width: Int, - val height: Int -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt deleted file mode 100644 index bf8465e1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/media/converter/movie/MovieMediaProcessService.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.service.media.converter.movie - -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MimeType -import dev.usbharu.hideout.core.service.media.ProcessedMedia -import dev.usbharu.hideout.core.service.media.ProcessedMediaPath -import dev.usbharu.hideout.core.service.media.converter.MediaProcessService -import org.bytedeco.ffmpeg.global.avcodec -import org.bytedeco.javacv.FFmpegFrameFilter -import org.bytedeco.javacv.FFmpegFrameGrabber -import org.bytedeco.javacv.FFmpegFrameRecorder -import org.bytedeco.javacv.Java2DFrameConverter -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.awt.image.BufferedImage -import java.nio.file.Files -import java.nio.file.Path -import javax.imageio.ImageIO -import kotlin.math.min - -@Service -@Qualifier("video") -class MovieMediaProcessService : MediaProcessService { - override fun isSupport(mimeType: MimeType): Boolean = mimeType.type == "video" - - override suspend fun process( - fileType: FileType, - contentType: String, - fileName: String, - file: ByteArray, - thumbnail: ByteArray? - ): ProcessedMedia { - TODO("Not yet implemented") - } - - @Suppress("LongMethod", "NestedBlockDepth", "CognitiveComplexMethod") - override suspend fun process( - mimeType: MimeType, - fileName: String, - filePath: Path, - thumbnails: Path? - ): ProcessedMediaPath { - val tempFile = Files.createTempFile("hideout-movie-processor-", ".tmp") - val thumbnailFile = Files.createTempFile("hideout-movie-thumbnail-generate-", ".tmp") - logger.info("START Convert Movie Media {}", fileName) - FFmpegFrameGrabber(filePath.toFile()).use { grabber -> - grabber.start() - val width = grabber.imageWidth - val height = grabber.imageHeight - val frameRate = 60.0 - - logger.debug("Movie Media Width {}, Height {}", width, height) - - FFmpegFrameFilter( - "fps=fps=${frameRate.toInt()}", - "anull", - width, - height, - grabber.audioChannels - ).use { filter -> - - filter.sampleFormat = grabber.sampleFormat - filter.sampleRate = grabber.sampleRate - filter.pixelFormat = grabber.pixelFormat - filter.frameRate = grabber.frameRate - filter.start() - - val videoBitRate = min(1300000, (width * height * frameRate * 1 * 0.07).toInt()) - - logger.debug("Movie Media BitRate {}", videoBitRate) - - FFmpegFrameRecorder(tempFile.toFile(), width, height, grabber.audioChannels).use { - it.sampleRate = grabber.sampleRate - it.format = "mp4" - it.videoCodec = avcodec.AV_CODEC_ID_H264 - it.audioCodec = avcodec.AV_CODEC_ID_AAC - it.audioChannels = grabber.audioChannels - it.videoQuality = 1.0 - it.frameRate = frameRate - it.setVideoOption("preset", "ultrafast") - it.timestamp = 0 - it.gopSize = frameRate.toInt() - it.videoBitrate = videoBitRate - it.start() - - var bufferedImage: BufferedImage? = null - - val frameConverter = Java2DFrameConverter() - - while (true) { - val grab = grabber.grab() ?: break - - if (bufferedImage == null) { - bufferedImage = frameConverter.convert(grab) - } - - if (grab.image != null || grab.samples != null) { - filter.push(grab) - } - while (true) { - val frame = filter.pull() ?: break - it.record(frame) - } - } - - if (bufferedImage != null) { - ImageIO.write(bufferedImage, "jpeg", thumbnailFile.toFile()) - } - } - } - } - - logger.info("SUCCESS Convert Movie Media {}", fileName) - - return ProcessedMediaPath( - tempFile, - thumbnailFile, - MimeType("video", "mp4", FileType.Video), - MimeType("image", "jpeg", FileType.Image) - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(MovieMediaProcessService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt deleted file mode 100644 index 8f4c382f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationRequest.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import java.time.Instant - -sealed class NotificationRequest(open val userId: Long, open val sourceActorId: Long?, val type: String) { - abstract fun buildNotification(id: Long, createdAt: Instant): Notification -} - -interface PostId { - val postId: Long -} - -data class MentionNotificationRequest( - override val userId: Long, - override val sourceActorId: Long, - override val postId: Long -) : NotificationRequest( - userId, - sourceActorId, - "mention" -), - PostId { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = postId, - text = null, - reactionId = null, - createdAt = createdAt - ) -} - -data class PostNotificationRequest( - override val userId: Long, - override val sourceActorId: Long, - override val postId: Long - -) : NotificationRequest(userId, sourceActorId, "post"), PostId { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = postId, - text = null, - reactionId = null, - createdAt = createdAt - ) -} - -data class RepostNotificationRequest( - override val userId: Long, - override val sourceActorId: Long, - override val postId: Long -) : NotificationRequest(userId, sourceActorId, "repost"), PostId { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = postId, - text = null, - reactionId = null, - createdAt = createdAt - ) -} - -data class FollowNotificationRequest( - override val userId: Long, - override val sourceActorId: Long -) : NotificationRequest(userId, sourceActorId, "follow") { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = null, - text = null, - reactionId = null, - createdAt = createdAt - ) -} - -data class FollowRequestNotificationRequest( - override val userId: Long, - override val sourceActorId: Long -) : NotificationRequest(userId, sourceActorId, "follow-request") { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = null, - text = null, - reactionId = null, - createdAt = createdAt - ) -} - -data class ReactionNotificationRequest( - override val userId: Long, - override val sourceActorId: Long, - override val postId: Long, - val reactionId: Long - -) : NotificationRequest(userId, sourceActorId, "reaction"), PostId { - override fun buildNotification(id: Long, createdAt: Instant): Notification = Notification( - id = id, - type = type, - userId = userId, - sourceActorId = sourceActorId, - postId = postId, - text = null, - reactionId = reactionId, - createdAt = createdAt - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt deleted file mode 100644 index 14354b26..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationService.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification - -interface NotificationService { - suspend fun publishNotify(notificationRequest: NotificationRequest): Notification? - suspend fun unpublishNotify(notificationId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt deleted file mode 100644 index 37cc9fb4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository -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.relationship.RelationshipRepository -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class NotificationServiceImpl( - private val relationshipNotificationManagementService: RelationshipNotificationManagementService, - private val relationshipRepository: RelationshipRepository, - private val notificationStoreList: List, - private val notificationRepository: NotificationRepository, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val reactionRepository: ReactionRepository, - private val applicationConfig: ApplicationConfig -) : NotificationService { - override suspend fun publishNotify(notificationRequest: NotificationRequest): Notification? { - logger.debug("NOTIFICATION REQUEST user: {} type: {}", notificationRequest.userId, notificationRequest.type) - logger.trace("NotificationRequest: {}", notificationRequest) - - val user = actorRepository.findById(notificationRequest.userId) - if (user == null || user.domain != applicationConfig.url.host) { - logger.debug("NOTIFICATION REQUEST is rejected. (Remote Actor or user not found.)") - return null - } - - // とりあえず個人間のRelationshipに基づいてきめる。今後増やす - if (!relationship(notificationRequest)) { - logger.debug("NOTIFICATION REQUEST is rejected. (relationship)") - return null - } - - val id = notificationRepository.generateId() - val createdAt = Instant.now() - - val notification = notificationRequest.buildNotification(id, createdAt) - - val savedNotification = notificationRepository.save(notification) - - val sourceActor = savedNotification.sourceActorId?.let { actorRepository.findById(it) } - - val post = savedNotification.postId?.let { postRepository.findById(it) } - val reaction = savedNotification.reactionId?.let { reactionRepository.findById(it) } - - logger.info( - "NOTIFICATION id: {} user: {} type: {}", - savedNotification.id, - savedNotification.userId, - savedNotification.type - ) - - logger.debug("push to {} notification store.", notificationStoreList.size) - for (it in notificationStoreList) { - @Suppress("TooGenericExceptionCaught") - try { - it.publishNotification(savedNotification, user, sourceActor, post, reaction) - } catch (e: Exception) { - logger.warn("FAILED Publish to notification.", e) - } - } - logger.debug("SUCCESS Notification id: {}", savedNotification.id) - - return savedNotification - } - - override suspend fun unpublishNotify(notificationId: Long) { - notificationRepository.deleteById(notificationId) - for (notificationStore in notificationStoreList) { - notificationStore.unpulishNotification(notificationId) - } - } - - /** - * 個人間のRelationshipに基づいて通知を送信するか判断します - * - * @param notificationRequest - * @return trueの場合送信する - */ - private suspend fun relationship(notificationRequest: NotificationRequest): Boolean { - val targetActorId = notificationRequest.sourceActorId ?: return true - val relationship = - relationshipRepository.findByUserIdAndTargetUserId(notificationRequest.userId, targetActorId) ?: return true - return relationshipNotificationManagementService.sendNotification(relationship, notificationRequest) - } - - companion object { - private val logger = LoggerFactory.getLogger(NotificationServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt deleted file mode 100644 index 3f03f549..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.reaction.Reaction - -interface NotificationStore { - suspend fun publishNotification( - notification: Notification, - user: Actor, - sourceActor: Actor?, - post: Post?, - reaction: Reaction? - ): Boolean - - suspend fun unpulishNotification(notificationId: Long): Boolean -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt deleted file mode 100644 index d7c89265..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.relationship.Relationship - -interface RelationshipNotificationManagementService { - fun sendNotification(relationship: Relationship, notificationRequest: NotificationRequest): Boolean -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt deleted file mode 100644 index 1a177666..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.service.post - -import dev.usbharu.hideout.core.domain.model.post.Post -import org.springframework.stereotype.Service - -@Service -interface PostService { - suspend fun createLocal(post: PostCreateDto): Post - suspend fun createRemote(post: Post): Post - suspend fun deleteLocal(post: Post) - suspend fun deleteRemote(post: Post) - suspend fun deleteByActor(actorId: Long) - suspend fun restoreByRemoteActor(actorId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt deleted file mode 100644 index d1d7a136..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.service.post - -import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.service.timeline.TimelineService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class PostServiceImpl( - private val postRepository: PostRepository, - private val actorRepository: ActorRepository, - private val timelineService: TimelineService, - private val postBuilder: Post.PostBuilder, - private val apSendCreateService: ApSendCreateService, - private val reactionRepository: ReactionRepository, - private val apSendDeleteService: APSendDeleteService, -) : PostService { - - override suspend fun createLocal(post: PostCreateDto): Post { - logger.info("START Create Local Post user: {}, media: {}", post.userId, post.mediaIds.size) - val create = internalCreate(post, true) - apSendCreateService.createNote(create) - logger.info("SUCCESS Create Local Post url: {}", create.url) - return create - } - - override suspend fun createRemote(post: Post): Post { - logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId) - val actor = - actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - val createdPost = internalCreate(post, false) - logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) - return createdPost - } - - override suspend fun deleteLocal(post: Post) { - if (post.deleted) { - return - } - reactionRepository.deleteByPostId(post.id) - postRepository.save(post.delete()) - val actor = actorRepository.findById(post.actorId) - ?: throw IllegalStateException("actor: ${post.actorId} was not found.") - - apSendDeleteService.sendDeleteNote(post) - - actorRepository.save(actor.decrementPostsCount()) - } - - override suspend fun deleteRemote(post: Post) { - if (post.deleted) { - return - } - reactionRepository.deleteByPostId(post.id) - postRepository.save(post.delete()) - - val actor = actorRepository.findById(post.actorId) - ?: throw IllegalStateException("actor: ${post.actorId} was not found.") - - actorRepository.save(actor.decrementPostsCount()) - } - - override suspend fun deleteByActor(actorId: Long) { - val actor = actorRepository.findById(actorId) - ?: throw IllegalStateException("actor: $actorId was not found.") - - postRepository.findByActorId(actorId).filterNot { it.deleted }.forEach { postRepository.save(it.delete()) } - - actorRepository.save(actor.copy(postsCount = 0, lastPostDate = null)) - } - - override suspend fun restoreByRemoteActor(actorId: Long) { - val actor = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - - val postList = postRepository.findByActorIdAndDeleted(actorId, true).map { it.restore() } - - postRepository.saveAll(postList) - - actorRepository.save(actor.copy(postsCount = actor.postsCount.plus(postList.size))) - } - - private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { - return try { - val save = postRepository.save(post) - timelineService.publishTimeline(post, isLocal) - save - } catch (_: DuplicateException) { - postRepository.findByApId(post.apId) ?: throw PostNotFoundException.withApId(post.apId) - } - } - - private suspend fun internalCreate(post: PostCreateDto, isLocal: Boolean): Post { - val user = actorRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found") - val id = postRepository.generateId() - val createPost = postBuilder.of( - id = id, - actorId = post.userId, - overview = post.overview, - content = post.text, - createdAt = Instant.now().toEpochMilli(), - visibility = post.visibility, - url = "${user.url}/posts/$id", - mediaIds = post.mediaIds, - replyId = post.repolyId, - repostId = post.repostId, - ) - return internalCreate(createPost, isLocal) - } - - companion object { - private val logger = LoggerFactory.getLogger(PostServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt deleted file mode 100644 index 5b3421cc..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionService.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.service.reaction - -import dev.usbharu.hideout.core.domain.model.emoji.Emoji -import org.springframework.stereotype.Service - -@Service -interface ReactionService { - suspend fun receiveReaction(emoji: Emoji, actorId: Long, postId: Long) - suspend fun receiveRemoveReaction(actorId: Long, postId: Long) - suspend fun sendReaction(emoji: Emoji, actorId: Long, postId: Long) - suspend fun removeReaction(actorId: Long, postId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt deleted file mode 100644 index a2ccd6aa..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.service.reaction - -import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.model.emoji.Emoji -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.ReactionRepository -import dev.usbharu.hideout.core.service.notification.NotificationService -import dev.usbharu.hideout.core.service.notification.ReactionNotificationRequest -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class ReactionServiceImpl( - private val reactionRepository: ReactionRepository, - private val apReactionService: APReactionService, - private val notificationService: NotificationService, - private val postRepository: PostRepository -) : ReactionService { - override suspend fun receiveReaction( - emoji: Emoji, - actorId: Long, - postId: Long - ) { - if (reactionRepository.existByPostIdAndActor(postId, actorId)) { - reactionRepository.deleteByPostIdAndActorId(postId, actorId) - } - try { - val reaction = reactionRepository.save(Reaction(reactionRepository.generateId(), emoji, postId, actorId)) - - notificationService.publishNotify( - ReactionNotificationRequest( - postRepository.findById(postId)!!.actorId, - actorId, - postId, - reaction.id - ) - ) - } catch (_: DuplicateException) { - } - } - - override suspend fun receiveRemoveReaction(actorId: Long, postId: Long) { - val reaction = reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) - if (reaction == null) { - LOGGER.warn("FAILED receive Remove Reaction. $actorId $postId") - return - } - reactionRepository.delete(reaction) - } - - override suspend fun sendReaction(emoji: Emoji, actorId: Long, postId: Long) { - val findByPostIdAndUserIdAndEmojiId = - reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) - - if (findByPostIdAndUserIdAndEmojiId != null) { - apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId) - reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) - } - - val reaction = Reaction(reactionRepository.generateId(), emoji, postId, actorId) - reactionRepository.save(reaction) - apReactionService.reaction(reaction) - - val id = postRepository.findById(postId)!!.actorId - - notificationService.publishNotify(ReactionNotificationRequest(id, actorId, postId, reaction.id)) - } - - override suspend fun removeReaction(actorId: Long, postId: Long) { - val findByPostIdAndUserIdAndEmojiId = - reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) - if (findByPostIdAndUserIdAndEmojiId == null) { - LOGGER.warn("FAILED Remove reaction. actorId: $actorId postId: $postId") - return - } - reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) - apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId) - } - - companion object { - val LOGGER: Logger = LoggerFactory.getLogger(ReactionServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt deleted file mode 100644 index 513ade8c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.service.relationship - -interface RelationshipService { - suspend fun followRequest(actorId: Long, targetId: Long) - suspend fun block(actorId: Long, targetId: Long) - - /** - * フォローリクエストを承認します - * [actorId]が[targetId]からのフォローリクエストを承認します - * - * @param actorId 承認操作をするユーザー - * @param targetId 承認するフォローリクエストを送ってきたユーザー - * @param force 強制的にAcceptアクティビティを発行する - */ - suspend fun acceptFollowRequest(actorId: Long, targetId: Long, force: Boolean = false) - suspend fun rejectFollowRequest(actorId: Long, targetId: Long) - suspend fun ignoreFollowRequest(actorId: Long, targetId: Long) - suspend fun unfollow(actorId: Long, targetId: Long) - suspend fun unblock(actorId: Long, targetId: Long) - suspend fun mute(actorId: Long, targetId: Long) - suspend fun unmute(actorId: Long, targetId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt deleted file mode 100644 index 057a5224..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ /dev/null @@ -1,341 +0,0 @@ -/* - * 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.service.relationship - -import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService -import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService -import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService -import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -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.relationship.Relationship -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.follow.SendFollowDto -import dev.usbharu.hideout.core.service.notification.FollowNotificationRequest -import dev.usbharu.hideout.core.service.notification.FollowRequestNotificationRequest -import dev.usbharu.hideout.core.service.notification.NotificationService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class RelationshipServiceImpl( - private val applicationConfig: ApplicationConfig, - private val relationshipRepository: RelationshipRepository, - private val apSendFollowService: APSendFollowService, - private val apSendAcceptService: ApSendAcceptService, - private val apSendRejectService: ApSendRejectService, - private val apSendUndoService: APSendUndoService, - private val actorRepository: ActorRepository, - private val notificationService: NotificationService, -) : RelationshipService { - override suspend fun followRequest(actorId: Long, targetId: Long) { - logger.info("START Follow Request userId: {} targetId: {}", actorId, targetId) - - val relationship = - relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(followRequest = true) - ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - if (inverseRelationship.blocking) { - logger.debug("FAILED Blocked by target. userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking) { - logger.debug("FAILED Blocking user. userId: {} targetId: {}", actorId, targetId) - return - } - if (relationship.ignoreFollowRequestToTarget) { - logger.debug("SUCCESS Ignore Follow Request. userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.following) { - logger.debug("SUCCESS User already follow. userId: {} targetId: {}", actorId, targetId) - acceptFollowRequest(targetId, actorId, true) - return - } - - relationshipRepository.save(relationship) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - apSendFollowService.sendFollow(SendFollowDto(user, remoteUser)) - } else { - val target = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - if (target.locked.not()) { - acceptFollowRequest(targetId, actorId) - } else { - notificationService.publishNotify(FollowRequestNotificationRequest(targetId, actorId)) - } - } - - logger.info("SUCCESS Follow Request userId: {} targetId: {}", actorId, targetId) - } - - override suspend fun block(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - if (relationship?.following == true) { - actorRepository.save(user.decrementFollowing()) - actorRepository.save(targetActor.decrementFollowers()) - } - - val blockedRelationship = relationship - ?.copy(blocking = true, followRequest = false, following = false) ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - if (inverseRelationship?.following == true) { - actorRepository.save(targetActor.decrementFollowing()) - actorRepository.save(user.decrementFollowers()) - } - - val blockedInverseRelationship = inverseRelationship - ?.copy(followRequest = false, following = false) - - relationshipRepository.save(blockedRelationship) - if (blockedInverseRelationship != null) { - relationshipRepository.save(blockedInverseRelationship) - } - } - - override suspend fun acceptFollowRequest(actorId: Long, targetId: Long, force: Boolean) { - logger.info("START Accept follow request userId: {} targetId: {}", actorId, targetId) - - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - if (relationship == null) { - logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.followRequest.not() && force.not()) { - logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking) { - logger.warn("FAILED Blocking user userId: {} targetId: {}", actorId, targetId) - throw IllegalStateException( - "Cannot accept a follow request from a blocked user. userId: $actorId targetId: $targetId" - ) - } - - if (inverseRelationship.blocking) { - logger.warn("FAILED BLocked by user userId: {} targetId: {}", actorId, targetId) - throw IllegalStateException( - "Cannot accept a follow request from a blocking user. userId: $actorId targetId: $targetId" - ) - } - - val copy = relationship.copy(followRequest = false, following = true, blocking = false) - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - - actorRepository.save(user.incrementFollowers()) - - relationshipRepository.save(copy) - - val remoteActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - - actorRepository.save(remoteActor.incrementFollowing()) - - if (isRemoteActor(remoteActor)) { - apSendAcceptService.sendAcceptFollow(user, remoteActor) - } - notificationService.publishNotify(FollowNotificationRequest(actorId, targetId)) - } - - override suspend fun rejectFollowRequest(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - if (relationship == null) { - logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.followRequest.not() && relationship.following.not()) { - logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(followRequest = false, following = false) - - relationshipRepository.save(copy) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - apSendRejectService.sendRejectFollow(user, remoteUser) - } - } - - override suspend fun ignoreFollowRequest(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - ?.copy(ignoreFollowRequestToTarget = true) - ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = true - ) - - relationshipRepository.save(relationship) - } - - override suspend fun unfollow(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - if (relationship == null) { - logger.warn("FAILED Unfollow. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - - if (relationship.following) { - actorRepository.save(user.decrementFollowing()) - actorRepository.save(targetActor.decrementFollowers()) - } - - if (relationship.following.not()) { - logger.warn("SUCCESS User already unfollow. userId: {} targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(following = false) - - relationshipRepository.save(copy) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - apSendUndoService.sendUndoFollow(user, remoteUser) - } - } - - override suspend fun unblock(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - if (relationship == null) { - logger.warn("FAILED Unblock. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking.not()) { - logger.warn("SUCCESS User is not blocking. userId: {] targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(blocking = false) - relationshipRepository.save(copy) - } - - override suspend fun mute(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(muting = true) - ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = false, - muting = true, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - relationshipRepository.save(relationship) - } - - override suspend fun unmute(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(muting = false) - - if (relationship == null) { - logger.warn("FAILED Mute. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - relationshipRepository.save(relationship) - } - - private fun isRemoteActor(actor: Actor): Boolean = actor.domain != applicationConfig.url.host - - private suspend fun isRemoteUser(userId: Long): Actor? { - logger.trace("isRemoteUser({})", userId) - val user = - actorRepository.findById(userId) ?: throw UserNotFoundException.withId(userId) - - logger.trace("user info {}", user) - - if (user.domain == applicationConfig.url.host) { - logger.trace("user: {} is local user", userId) - return null - } - logger.trace("user: {} is remote user", userId) - return user - } - - companion object { - private val logger = LoggerFactory.getLogger(RelationshipServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt deleted file mode 100644 index 3e6408fe..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/CacheManager.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.service.resource - -interface CacheManager { - suspend fun putCache(key: String, block: suspend () -> ResolveResponse) - suspend fun getOrWait(key: String): ResolveResponse -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt deleted file mode 100644 index 39c1239f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/InMemoryCacheManager.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.service.resource - -import dev.usbharu.hideout.util.LruCache -import kotlinx.coroutines.delay -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class InMemoryCacheManager : CacheManager { - private val cacheKey = LruCache(15) - private val valueStore = mutableMapOf() - private val keyMutex = Mutex() - - override suspend fun putCache(key: String, block: suspend () -> ResolveResponse) { - val needRunBlock: Boolean - keyMutex.withLock { - cacheKey.filter { Instant.ofEpochMilli(it.value).plusSeconds(300) <= Instant.now() } - - val cached = cacheKey[key] - if (cached == null) { - needRunBlock = true - cacheKey[key] = Instant.now().toEpochMilli() - - valueStore.remove(key) - } else { - needRunBlock = false - } - } - if (needRunBlock) { - @Suppress("TooGenericExceptionCaught") - val processed = try { - block() - } catch (e: Exception) { - cacheKey.remove(key) - throw e - } - - if (cacheKey.containsKey(key)) { - valueStore[key] = processed - } - } - } - - override suspend fun getOrWait(key: String): ResolveResponse { - while (valueStore.contains(key).not()) { - if (cacheKey.containsKey(key).not()) { - throw IllegalStateException("Invalid cache key. $key") - } - delay(1) - } - return valueStore.getValue(key) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt deleted file mode 100644 index c2453e7a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResolveResponse.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.service.resource - -import io.ktor.client.statement.* -import io.ktor.util.* -import io.ktor.utils.io.jvm.javaio.* -import java.io.InputStream - -class KtorResolveResponse(val ktorHttpResponse: HttpResponse) : ResolveResponse { - - private lateinit var _bodyAsText: String - private lateinit var _bodyAsBytes: ByteArray - - override suspend fun body(): InputStream = ktorHttpResponse.bodyAsChannel().toInputStream() - override suspend fun bodyAsText(): String { - if (!this::_bodyAsText.isInitialized) { - _bodyAsText = ktorHttpResponse.bodyAsText() - } - return _bodyAsText - } - - override suspend fun bodyAsBytes(): ByteArray { - if (!this::_bodyAsBytes.isInitialized) { - _bodyAsBytes = ktorHttpResponse.readBytes() - } - return _bodyAsBytes - } - - override suspend fun header(): Map> = ktorHttpResponse.headers.toMap() - override suspend fun status(): Int = ktorHttpResponse.status.value - override suspend fun statusMessage(): String = ktorHttpResponse.status.description - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as KtorResolveResponse - - if (ktorHttpResponse != other.ktorHttpResponse) return false - if (_bodyAsText != other._bodyAsText) return false - if (!_bodyAsBytes.contentEquals(other._bodyAsBytes)) return false - - return true - } - - override fun hashCode(): Int { - var result = ktorHttpResponse.hashCode() - result = 31 * result + _bodyAsText.hashCode() - result = 31 * result + _bodyAsBytes.contentHashCode() - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt deleted file mode 100644 index f8407b23..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveService.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.service.resource - -import dev.usbharu.hideout.application.config.MediaConfig -import dev.usbharu.hideout.core.domain.exception.media.RemoteMediaFileSizeException -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.http.* -import org.springframework.stereotype.Service - -@Service -class KtorResourceResolveService( - private val httpClient: HttpClient, - private val cacheManager: CacheManager, - private val mediaConfig: MediaConfig -) : - ResourceResolveService { - - var sizeLimit = mediaConfig.remoteMediaFileSizeLimit - - override suspend fun resolve(url: String): ResolveResponse { - cacheManager.putCache(getCacheKey(url)) { - runResolve(url) - } - return cacheManager.getOrWait(getCacheKey(url)) - } - - protected suspend fun runResolve(url: String): ResolveResponse { - val httpResponse = httpClient.get(url) - val contentLength = httpResponse.contentLength() - if ((contentLength ?: 0) >= sizeLimit) { - throw RemoteMediaFileSizeException("File size is too large. $contentLength >= $sizeLimit") - } - return KtorResolveResponse(httpResponse) - } - - protected suspend fun getCacheKey(url: String) = url -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt deleted file mode 100644 index 8da3c7cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResolveResponse.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.service.resource - -import java.io.InputStream - -interface ResolveResponse { - suspend fun body(): InputStream - suspend fun bodyAsText(): String - suspend fun bodyAsBytes(): ByteArray - suspend fun header(): Map> - suspend fun status(): Int - suspend fun statusMessage(): String -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt deleted file mode 100644 index bcb1c97f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/resource/ResourceResolveService.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.service.resource - -interface ResourceResolveService { - suspend fun resolve(url: String): ResolveResponse -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt deleted file mode 100644 index 98447037..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.service.timeline - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Timelines -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.selectAll -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Service - -@Service -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) -class ExposedGenerateTimelineService(private val statusQueryService: StatusQueryService) : GenerateTimelineService { - - override suspend fun getTimeline( - forUserId: Long?, - localOnly: Boolean, - mediaOnly: Boolean, - page: Page - ): PaginationList { - val query = Timelines.selectAll() - - if (forUserId != null) { - query.andWhere { Timelines.userId eq forUserId } - } - if (localOnly) { - query.andWhere { Timelines.isLocal eq true } - } - val result = query.withPagination(page, Timelines.id) - - val statusQueries = result.map { - StatusQuery( - it[Timelines.postId], - it[Timelines.replyId], - it[Timelines.repostId], - it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() }, - it[Timelines.emojiIds].split(",").mapNotNull { s -> s.toLongOrNull() } - ) - } - - val findByPostIdsWithMediaIds = statusQueryService.findByPostIdsWithMediaIds(statusQueries) - return PaginationList( - findByPostIdsWithMediaIds, - findByPostIdsWithMediaIds.lastOrNull()?.id?.toLongOrNull(), - findByPostIdsWithMediaIds.firstOrNull()?.id?.toLongOrNull() - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt deleted file mode 100644 index 39ce7e52..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.service.timeline - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.data.domain.Sort -import org.springframework.data.mongodb.core.MongoTemplate -import org.springframework.data.mongodb.core.query.Criteria -import org.springframework.data.mongodb.core.query.Query -import org.springframework.stereotype.Service - -@Service -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) -class MongoGenerateTimelineService( - private val statusQueryService: StatusQueryService, - private val mongoTemplate: MongoTemplate -) : - GenerateTimelineService { - - override suspend fun getTimeline( - forUserId: Long?, - localOnly: Boolean, - mediaOnly: Boolean, - page: Page - ): PaginationList { - val query = Query() - - if (forUserId != null) { - val criteria = Criteria.where("userId").`is`(forUserId) - query.addCriteria(criteria) - } - if (localOnly) { - val criteria = Criteria.where("isLocal").`is`(true) - query.addCriteria(criteria) - } - - if (page.minId != null) { - page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - } else { - query.with(Sort.by(Sort.Direction.DESC, "createdAt")) - page.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - } - - page.limit?.let { query.limit(it) } - - query.with(Sort.by(Sort.Direction.DESC, "createdAt")) - - val timelines = mongoTemplate.find(query, Timeline::class.java) - - val statuses = statusQueryService.findByPostIdsWithMediaIds( - timelines.map { - StatusQuery( - it.postId, - it.replyId, - it.repostId, - it.mediaIds, - it.emojiIds - ) - } - ) - return PaginationList( - statuses, - statuses.lastOrNull()?.id?.toLongOrNull(), - statuses.firstOrNull()?.id?.toLongOrNull() - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt deleted file mode 100644 index 1bdec5c0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.service.timeline - -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import dev.usbharu.hideout.core.query.FollowerQueryService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class TimelineService( - private val followerQueryService: FollowerQueryService, - private val timelineRepository: TimelineRepository, - private val actorRepository: ActorRepository -) { - suspend fun publishTimeline(post: Post, isLocal: Boolean) { - val findFollowersById = followerQueryService.findFollowersById(post.actorId).toMutableList() - if (isLocal) { - // 自分自身も含める必要がある - val user = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - findFollowersById.add(user) - } - val timelines = findFollowersById.map { - Timeline( - id = timelineRepository.generateId(), - userId = it.id, - timelineId = 0, - postId = post.id, - postActorId = post.actorId, - createdAt = post.createdAt, - replyId = post.replyId, - repostId = post.repostId, - visibility = post.visibility, - sensitive = post.sensitive, - isLocal = isLocal, - isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds, - emojiIds = post.emojiIds - ) - }.toMutableList() - if (post.visibility == Visibility.PUBLIC) { - timelines.add( - Timeline( - id = timelineRepository.generateId(), - userId = 0, - timelineId = 0, - postId = post.id, - postActorId = post.actorId, - createdAt = post.createdAt, - replyId = post.replyId, - repostId = post.repostId, - visibility = post.visibility, - sensitive = post.sensitive, - isLocal = isLocal, - isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds, - emojiIds = post.emojiIds - ) - ) - } - timelineRepository.saveAll(timelines) - logger.debug("SUCCESS Timeline published. {}", timelines.size) - } - - companion object { - private val logger = LoggerFactory.getLogger(TimelineService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt deleted file mode 100644 index e02c259c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.service.user - -data class RemoteUserCreateDto( - val name: String, - val domain: String, - val screenName: String, - val description: String, - val inbox: String, - val outbox: String, - val url: String, - val publicKey: String, - val keyId: String, - val followers: String?, - val following: String?, - val sharedInbox: String?, - val locked: Boolean? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt deleted file mode 100644 index 93e4959c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.service.user - -import dev.usbharu.hideout.core.domain.model.media.Media - -data class UpdateUserDto( - val screenName: String, - val description: String, - val avatarMedia: Media?, - val headerMedia: Media?, - val locked: Boolean, - val autoAcceptFolloweeFollowRequest: Boolean -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt deleted file mode 100644 index ad372b37..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.service.user - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder -import org.springframework.stereotype.Service -import java.security.* -import java.util.* - -@Service -class UserAuthServiceImpl( - private val actorRepository: ActorRepository, - private val applicationConfig: ApplicationConfig -) : UserAuthService { - - override fun hash(password: String): String = BCryptPasswordEncoder().encode(password) - - override suspend fun usernameAlreadyUse(username: String): Boolean { - actorRepository.findByNameAndDomain(username, applicationConfig.url.host) ?: return false - return true - } - - override suspend fun generateKeyPair(): KeyPair { - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(keySize) - return keyPairGenerator.generateKeyPair() - } - - companion object { - val sha256: MessageDigest = MessageDigest.getInstance("SHA-256") - const val keySize: Int = 2048 - const val pemSize: Int = 64 - } -} - -fun PublicKey.toPem(): String { - return "-----BEGIN PUBLIC KEY-----\n" + - Base64.getEncoder().encodeToString(encoded).chunked(UserAuthServiceImpl.pemSize).joinToString("\n") + - "\n-----END PUBLIC KEY-----\n" -} - -fun PrivateKey.toPem(): String { - return "-----BEGIN PRIVATE KEY-----\n" + - Base64.getEncoder().encodeToString(encoded).chunked(UserAuthServiceImpl.pemSize).joinToString("\n") + - "\n-----END PRIVATE KEY-----\n" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserCreateDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserCreateDto.kt deleted file mode 100644 index be7b5e0d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserCreateDto.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.service.user - -data class UserCreateDto( - val name: String, - val screenName: String, - val description: String, - val password: String -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt deleted file mode 100644 index 889c5257..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.service.user - -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -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.deletedActor.DeletedActor -import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository -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.relationship.RelationshipRepository -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository -import dev.usbharu.hideout.core.external.job.UpdateActorTask -import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -@Suppress("LongParameterList") -class UserServiceImpl( - private val actorRepository: ActorRepository, - private val userAuthService: UserAuthService, - private val actorBuilder: Actor.UserBuilder, - private val applicationConfig: ApplicationConfig, - private val instanceService: InstanceService, - private val userDetailRepository: UserDetailRepository, - private val deletedActorRepository: DeletedActorRepository, - private val reactionRepository: ReactionRepository, - private val relationshipRepository: RelationshipRepository, - private val postService: PostService, - private val apSendDeleteService: APSendDeleteService, - private val postRepository: PostRepository, - private val owlProducer: OwlProducer, -) : - UserService { - - override suspend fun usernameAlreadyUse(username: String): Boolean { - val findByNameAndDomain = actorRepository.findByNameAndDomain(username, applicationConfig.url.host) - return findByNameAndDomain != null - } - - override suspend fun createLocalUser(user: UserCreateDto): Actor { - if (applicationConfig.private) { - throw IllegalStateException("Instance is a private mode.") - } - - val nextId = actorRepository.nextId() - val hashedPassword = userAuthService.hash(user.password) - val keyPair = userAuthService.generateKeyPair() - val userUrl = "${applicationConfig.url}/users/${user.name}" - val userEntity = actorBuilder.of( - id = nextId, - name = user.name, - domain = applicationConfig.url.host, - screenName = user.screenName, - description = user.description, - inbox = "$userUrl/inbox", - outbox = "$userUrl/outbox", - url = userUrl, - publicKey = keyPair.public.toPem(), - privateKey = keyPair.private.toPem(), - createdAt = Instant.now(), - following = "$userUrl/following", - followers = "$userUrl/followers", - keyId = "$userUrl#pubkey", - locked = false, - instance = 0 - ) - val save = actorRepository.save(userEntity) - userDetailRepository.save(UserDetail(nextId, hashedPassword, true)) - return save - } - - override suspend fun createRemoteUser(user: RemoteUserCreateDto, idOverride: Long?): Actor { - logger.info("START Create New remote user. name: {} url: {}", user.name, user.url) - - val deletedActor = deletedActorRepository.findByNameAndDomain(user.name, user.domain) - - if (deletedActor != null) { - logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}") - throw IllegalStateException("Cannot create Deleted actor.") - } - - val instance = instanceService.fetchInstance(user.url, user.sharedInbox) - - val nextId = actorRepository.nextId() - val userEntity = actorBuilder.of( - id = idOverride ?: nextId, - name = user.name, - domain = user.domain, - screenName = user.screenName, - description = user.description, - inbox = user.inbox, - outbox = user.outbox, - url = user.url, - publicKey = user.publicKey, - createdAt = Instant.now(), - followers = user.followers, - following = user.following, - keyId = user.keyId, - instance = instance.id, - locked = user.locked ?: false - ) - return try { - val save = actorRepository.save(userEntity) - logger.warn("SUCCESS Create New remote user. id: {} name: {} url: {}", userEntity.id, user.name, user.url) - save - } catch (_: DuplicateException) { - actorRepository.findByUrl(user.url)!! - } - } - - override suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) { - val userDetail = userDetailRepository.findByActorId(userId) - ?: throw IllegalArgumentException("userId: $userId was not found.") - - val actor = actorRepository.findById(userId) ?: throw IllegalArgumentException("userId $userId was not found.") - - actorRepository.save( - actor.copy( - screenName = updateUserDto.screenName, - description = updateUserDto.description, - locked = updateUserDto.locked - ) - ) - - userDetailRepository.save( - userDetail.copy( - autoAcceptFolloweeFollowRequest = updateUserDto.autoAcceptFolloweeFollowRequest - ) - ) - } - - override suspend fun deleteRemoteActor(actorId: Long) { - val actor = actorRepository.findByIdWithLock(actorId) ?: throw UserNotFoundException.withId(actorId) - val deletedActor = DeletedActor( - id = actor.id, - name = actor.name, - domain = actor.domain, - apiId = actor.url, - publicKey = actor.publicKey, - deletedAt = Instant.now() - ) - relationshipRepository.deleteByActorIdOrTargetActorId(actorId, actorId) - - reactionRepository.deleteByActorId(actorId) - - postService.deleteByActor(actorId) - - actorRepository.delete(actor.id) - deletedActorRepository.save(deletedActor) - } - - override suspend fun restorationRemoteActor(actorId: Long) { - val deletedActor = deletedActorRepository.findById(actorId) - ?: return - - deletedActorRepository.delete(deletedActor) - - owlProducer.publishTask(UpdateActorTask(deletedActor.id, deletedActor.apiId)) - } - - override suspend fun deleteLocalUser(userId: Long) { - val actor = actorRepository.findByIdWithLock(userId) ?: throw UserNotFoundException.withId(userId) - apSendDeleteService.sendDeleteActor(actor) - val deletedActor = DeletedActor( - id = actor.id, - name = actor.name, - domain = actor.domain, - apiId = actor.url, - publicKey = actor.publicKey, - deletedAt = Instant.now() - ) - relationshipRepository.deleteByActorIdOrTargetActorId(userId, userId) - - reactionRepository.deleteByActorId(actor.id) - - postService.deleteByActor(actor.id) - actorRepository.delete(actor.id) - val userDetail = - userDetailRepository.findByActorId(actor.id) ?: throw IllegalStateException("user detail not found.") - userDetailRepository.delete(userDetail) - deletedActorRepository.save(deletedActor) - } - - override suspend fun updateUserStatistics(userId: Long) { - val actor = actorRepository.findByIdWithLock(userId) ?: throw UserNotFoundException.withId(userId) - - val followerCount = relationshipRepository.countByTargetIdAndFollowing(userId, true) - val followingCount = relationshipRepository.countByUserIdAndFollowing(userId, true) - val postsCount = postRepository.countByActorId(userId) - - actorRepository.save( - actor.copy( - followersCount = followerCount, - followingCount = followingCount, - postsCount = postsCount - ) - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/generate/JsonOrFormModelMethodProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/generate/JsonOrFormModelMethodProcessor.kt index 98febb98..d7a97736 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/generate/JsonOrFormModelMethodProcessor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/generate/JsonOrFormModelMethodProcessor.kt @@ -30,7 +30,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBody @Suppress("TooGenericExceptionCaught") class JsonOrFormModelMethodProcessor( private val modelAttributeMethodProcessor: ModelAttributeMethodProcessor, - private val requestResponseBodyMethodProcessor: RequestResponseBodyMethodProcessor + private val requestResponseBodyMethodProcessor: RequestResponseBodyMethodProcessor, ) : HandlerMethodArgumentResolver { private val isJsonRegex = Regex("application/((\\w*)\\+)?json") @@ -41,7 +41,7 @@ class JsonOrFormModelMethodProcessor( parameter: MethodParameter, mavContainer: ModelAndViewContainer?, webRequest: NativeWebRequest, - binderFactory: WebDataBinderFactory? + binderFactory: WebDataBinderFactory?, ): Any? { val contentType = webRequest.getHeader("Content-Type").orEmpty() logger.trace("ContentType is {}", contentType) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt deleted file mode 100644 index 8f6d5b79..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.mastodon.domain.exception - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -class AccountNotFoundException : ClientException { - constructor(response: MastodonApiErrorResponse) : super(response) - constructor(message: String?, response: MastodonApiErrorResponse) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse) : super( - message, - cause, - response - ) - - constructor(cause: Throwable?, response: MastodonApiErrorResponse) : super(cause, response) - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse, - ) : super(message, cause, enableSuppression, writableStackTrace, response) - - fun getTypedResponse(): MastodonApiErrorResponse = - response as MastodonApiErrorResponse - - companion object { - fun ofId(id: Long): AccountNotFoundException = AccountNotFoundException( - "id: $id was not found.", - MastodonApiErrorResponse( - NotFoundResponse( - "Record not found" - ), - 404 - ), - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt deleted file mode 100644 index 3414889a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -open class ClientException : MastodonApiException { - constructor(response: MastodonApiErrorResponse<*>) : super(response) - constructor(message: String?, response: MastodonApiErrorResponse<*>) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse<*>) : super( - message, - cause, - response - ) - - constructor(cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(cause, response) - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse<*>, - ) : super(message, cause, enableSuppression, writableStackTrace, response) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt deleted file mode 100644 index c3afd55a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -@Suppress("UnnecessaryAbstractClass") -abstract class MastodonApiException : RuntimeException { - - val response: MastodonApiErrorResponse<*> - - constructor(response: MastodonApiErrorResponse<*>) : super() { - this.response = response - } - - constructor(message: String?, response: MastodonApiErrorResponse<*>) : super(message) { - this.response = response - } - - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(message, cause) { - this.response = response - } - - constructor(cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(cause) { - this.response = response - } - - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse<*>, - ) : super( - message, - cause, - enableSuppression, - writableStackTrace - ) { - this.response = response - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt deleted file mode 100644 index d2d6b187..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -open class ServerException(response: MastodonApiErrorResponse<*>) : MastodonApiException(response) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt deleted file mode 100644 index 934403ec..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.mastodon.domain.exception - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -class StatusNotFoundException : ClientException { - - constructor(response: MastodonApiErrorResponse) : super(response) - - constructor(message: String?, response: MastodonApiErrorResponse) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse) : super( - message, - cause, - response - ) - constructor(cause: Throwable?, response: MastodonApiErrorResponse) : super(cause, response) - - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse, - ) : super(message, cause, enableSuppression, writableStackTrace, response) - - fun getTypedResponse(): MastodonApiErrorResponse = - response as MastodonApiErrorResponse - - companion object { - fun ofId(id: Long): StatusNotFoundException = StatusNotFoundException( - "id: $id was not found.", - MastodonApiErrorResponse( - NotFoundResponse( - "Record not found" - ), - 404 - ), - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt deleted file mode 100644 index ee19ebcf..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.mastodon.domain.model - -data class MastodonApiErrorResponse(val response: R, val statusCode: Int) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt deleted file mode 100644 index e334b04a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.mastodon.domain.model - -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document -import java.time.Instant - -@Document -data class MastodonNotification( - @Id - val id: Long, - val userId: Long, - val type: NotificationType, - val createdAt: Instant, - val accountId: Long, - val statusId: Long?, - val reportId: Long?, - val relationshipServeranceEvent: Long? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt deleted file mode 100644 index d1e0ac54..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.mastodon.domain.model - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList - -interface MastodonNotificationRepository { - suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification - suspend fun deleteById(id: Long) - suspend fun findById(id: Long): MastodonNotification? - - @Suppress("FunctionMaxLength") - suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList - - suspend fun deleteByUserId(userId: Long) - suspend fun deleteByUserIdAndId(userId: Long, id: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt deleted file mode 100644 index a38cfa82..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.mastodon.domain.model - -@Suppress("EnumEntryName", "EnumNaming", "EnumEntryNameCase") -enum class NotificationType { - mention, - status, - reblog, - follow, - follow_request, - favourite, - poll, - update, - admin_sign_up, - admin_report, - severed_relationships; - - companion object { - fun parse(string: String): NotificationType? = when (string) { - "mention" -> mention - "status" -> status - "reblog" -> reblog - "follow" -> follow - "follow_request" -> follow_request - "favourite" -> favourite - "poll" -> poll - "update" -> update - "admin.sign_up" -> admin_sign_up - "admin.report" -> admin_report - "servered_relationships" -> severed_relationships - else -> null - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt deleted file mode 100644 index 7f928b08..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.mastodon.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination -import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Repository - -@Repository -@Qualifier("jdbc") -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) -class ExposedMastodonNotificationRepository : MastodonNotificationRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = query { - val singleOrNull = - MastodonNotifications.selectAll().where { MastodonNotifications.id eq mastodonNotification.id } - .singleOrNull() - if (singleOrNull == null) { - MastodonNotifications.insert { - it[id] = mastodonNotification.id - it[type] = mastodonNotification.type.name - it[createdAt] = mastodonNotification.createdAt - it[accountId] = mastodonNotification.accountId - it[statusId] = mastodonNotification.statusId - it[reportId] = mastodonNotification.reportId - it[relationshipServeranceEventId] = - mastodonNotification.relationshipServeranceEvent - } - } else { - MastodonNotifications.update({ MastodonNotifications.id eq mastodonNotification.id }) { - it[type] = mastodonNotification.type.name - it[createdAt] = mastodonNotification.createdAt - it[accountId] = mastodonNotification.accountId - it[statusId] = mastodonNotification.statusId - it[reportId] = mastodonNotification.reportId - it[relationshipServeranceEventId] = - mastodonNotification.relationshipServeranceEvent - } - } - mastodonNotification - } - - override suspend fun deleteById(id: Long): Unit = query { - MastodonNotifications.deleteWhere { - MastodonNotifications.id eq id - } - } - - override suspend fun findById(id: Long): MastodonNotification? = query { - MastodonNotifications.selectAll().where { MastodonNotifications.id eq id }.singleOrNull() - ?.toMastodonNotification() - } - - override suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList = query { - val query = MastodonNotifications.selectAll().where { MastodonNotifications.userId eq loginUser } - val result = query.withPagination(page, MastodonNotifications.id) - - return@query PaginationList(result.map { it.toMastodonNotification() }, result.next, result.prev) - } - - override suspend fun deleteByUserId(userId: Long) { - MastodonNotifications.deleteWhere { - MastodonNotifications.userId eq userId - } - } - - override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { - MastodonNotifications.deleteWhere { - MastodonNotifications.userId eq userId and (MastodonNotifications.id eq id) - } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java) - } -} - -fun ResultRow.toMastodonNotification(): MastodonNotification = MastodonNotification( - id = this[MastodonNotifications.id], - userId = this[MastodonNotifications.userId], - type = NotificationType.valueOf(this[MastodonNotifications.type]), - createdAt = this[MastodonNotifications.createdAt], - accountId = this[MastodonNotifications.accountId], - statusId = this[MastodonNotifications.statusId], - reportId = this[MastodonNotifications.reportId], - relationshipServeranceEvent = this[MastodonNotifications.relationshipServeranceEventId], -) - -object MastodonNotifications : Table("mastodon_notifications") { - val id = long("id") - val userId = long("user_id") - val type = varchar("type", 100) - val createdAt = timestamp("created_at") - val accountId = long("account_id") - val statusId = long("status_id").nullable() - val reportId = long("report_id").nullable() - val relationshipServeranceEventId = long("relationship_serverance_event_id").nullable() -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt deleted file mode 100644 index e95d204f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.mastodon.infrastructure.mongorepository - -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import org.springframework.data.mongodb.repository.MongoRepository - -interface MongoMastodonNotificationRepository : MongoRepository { - - fun deleteByUserId(userId: Long): Long - - fun deleteByIdAndUserId(id: Long, userId: Long): Long -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt deleted file mode 100644 index 4fd243e9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.mastodon.infrastructure.mongorepository - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.data.domain.Sort -import org.springframework.data.mongodb.core.MongoTemplate -import org.springframework.data.mongodb.core.query.Criteria -import org.springframework.data.mongodb.core.query.Query -import org.springframework.stereotype.Repository -import kotlin.jvm.optionals.getOrNull - -@Repository -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) -class MongoMastodonNotificationRepositoryWrapper( - private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository, - private val mongoTemplate: MongoTemplate -) : - MastodonNotificationRepository { - override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = - mongoMastodonNotificationRepository.save(mastodonNotification) - - override suspend fun deleteById(id: Long) = mongoMastodonNotificationRepository.deleteById(id) - - override suspend fun findById(id: Long): MastodonNotification? = - mongoMastodonNotificationRepository.findById(id).getOrNull() - - override suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList { - val query = Query() - - page.limit?.let { query.limit(it) } - - val mastodonNotifications = if (page.minId != null) { - query.with(Sort.by(Sort.Direction.ASC, "id")) - page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - mongoTemplate.find(query, MastodonNotification::class.java).reversed() - } else { - query.with(Sort.by(Sort.Direction.DESC, "id")) - page.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - mongoTemplate.find(query, MastodonNotification::class.java) - } - - return PaginationList( - mastodonNotifications, - mastodonNotifications.firstOrNull()?.id, - mastodonNotifications.lastOrNull()?.id - ) - } - - override suspend fun deleteByUserId(userId: Long) { - mongoMastodonNotificationRepository.deleteByUserId(userId) - } - - override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { - mongoMastodonNotificationRepository.deleteByIdAndUserId(id, userId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt deleted file mode 100644 index b154f52b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.mastodon.infrastructure.springweb - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.domain.mastodon.model.generated.UnprocessableEntityResponse -import dev.usbharu.hideout.domain.mastodon.model.generated.UnprocessableEntityResponseDetails -import dev.usbharu.hideout.mastodon.domain.exception.AccountNotFoundException -import dev.usbharu.hideout.mastodon.domain.exception.StatusNotFoundException -import dev.usbharu.hideout.mastodon.interfaces.api.account.MastodonAccountApiController -import dev.usbharu.hideout.mastodon.interfaces.api.apps.MastodonAppsApiController -import dev.usbharu.hideout.mastodon.interfaces.api.filter.MastodonFilterApiController -import dev.usbharu.hideout.mastodon.interfaces.api.instance.MastodonInstanceApiController -import dev.usbharu.hideout.mastodon.interfaces.api.media.MastodonMediaApiController -import dev.usbharu.hideout.mastodon.interfaces.api.notification.MastodonNotificationApiController -import dev.usbharu.hideout.mastodon.interfaces.api.status.MastodonStatusesApiContoller -import dev.usbharu.hideout.mastodon.interfaces.api.timeline.MastodonTimelineApiController -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.validation.BindException -import org.springframework.validation.FieldError -import org.springframework.web.bind.annotation.ControllerAdvice -import org.springframework.web.bind.annotation.ExceptionHandler - -@ControllerAdvice( - assignableTypes = [ - MastodonAccountApiController::class, - MastodonAppsApiController::class, - MastodonFilterApiController::class, - MastodonInstanceApiController::class, - MastodonMediaApiController::class, - MastodonNotificationApiController::class, - MastodonStatusesApiContoller::class, - MastodonTimelineApiController::class - ] -) -class MastodonApiControllerAdvice { - - @ExceptionHandler(BindException::class) - fun handleException(ex: BindException): ResponseEntity { - logger.debug("Failed bind entity.", ex) - - val details = mutableMapOf>() - - ex.allErrors.forEach { - val defaultMessage = it.defaultMessage - when { - it is FieldError -> { - val code = when (it.code) { - "Email" -> "ERR_INVALID" - "Pattern" -> "ERR_INVALID" - else -> "ERR_INVALID" - } - details.getOrPut(it.field) { - mutableListOf() - }.add(UnprocessableEntityResponseDetails(code, defaultMessage.orEmpty())) - } - - defaultMessage?.startsWith("Parameter specified as non-null is null:") == true -> { - val parameter = defaultMessage.substringAfterLast("parameter ") - - details.getOrPut(parameter) { - mutableListOf() - }.add(UnprocessableEntityResponseDetails("ERR_BLANK", "can't be blank")) - } - - else -> { - logger.warn("Unknown validation error", ex) - } - } - } - - val message = details.map { - it.key + " " + it.value.joinToString { responseDetails -> responseDetails.description } - }.joinToString() - - return ResponseEntity.unprocessableEntity() - .body(UnprocessableEntityResponse(message, details)) - } - - @ExceptionHandler(StatusNotFoundException::class) - fun handleException(ex: StatusNotFoundException): ResponseEntity { - logger.warn("Status not found.", ex) - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getTypedResponse().response) - } - - @ExceptionHandler(AccountNotFoundException::class) - fun handleException(ex: AccountNotFoundException): ResponseEntity { - logger.warn("Account not found.", ex) - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getTypedResponse().response) - } - - companion object { - private val logger = LoggerFactory.getLogger(MastodonApiControllerAdvice::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt deleted file mode 100644 index a2e391af..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * 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.mastodon.interfaces.api.account - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.AccountApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.core.service.user.UserCreateDto -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.service.account.AccountApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import java.net.URI - -@Controller -class MastodonAccountApiController( - private val accountApiService: AccountApiService, - private val transaction: Transaction, - private val loginUserContextHolder: LoginUserContextHolder, - private val applicationConfig: ApplicationConfig -) : AccountApi { - - override suspend fun apiV1AccountsIdFollowPost( - id: String, - followRequestBody: FollowRequestBody? - ): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(accountApiService.follow(userid, id.toLong())) - } - - override suspend fun apiV1AccountsIdGet(id: String): ResponseEntity = - ResponseEntity.ok(accountApiService.account(id.toLong())) - - override suspend fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity = ResponseEntity( - accountApiService.verifyCredentials(loginUserContextHolder.getLoginUserId()), - HttpStatus.OK - ) - - override suspend fun apiV1AccountsPost(accountsCreateRequest: AccountsCreateRequest): ResponseEntity { - transaction.transaction { - accountApiService.registerAccount( - UserCreateDto( - accountsCreateRequest.username, - accountsCreateRequest.username, - "", - accountsCreateRequest.password - ) - ) - } - val httpHeaders = HttpHeaders() - httpHeaders.location = URI("/users/${accountsCreateRequest.username}") - return ResponseEntity(Unit, httpHeaders, HttpStatus.FOUND) - } - - override fun apiV1AccountsIdStatusesGet( - id: String, - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String? - ): ResponseEntity> = runBlocking { - val userid = loginUserContextHolder.getLoginUserIdOrNull() - val statuses = accountApiService.accountsStatuses( - userid = id.toLong(), - onlyMedia = onlyMedia, - excludeReplies = excludeReplies, - excludeReblogs = excludeReblogs, - pinned = pinned, - tagged = tagged, - loginUser = userid, - page = Page.of( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - minId?.toLongOrNull(), - limit.coerceIn(0, 80) - ) - ) - val httpHeader = statuses.toHttpHeader( - { "${applicationConfig.url}/api/v1/accounts/$id/statuses?min_id=$it" }, - { "${applicationConfig.url}/api/v1/accounts/$id/statuses?max_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(statuses.asFlow()) - } - - ResponseEntity.ok(statuses.asFlow()) - } - - override fun apiV1AccountsRelationshipsGet( - id: List?, - withSuspended: Boolean - ): ResponseEntity> = runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - ResponseEntity.ok( - accountApiService.relationships(userid, id.orEmpty().mapNotNull { it.toLongOrNull() }, withSuspended) - .asFlow() - ) - } - - override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val block = accountApiService.block(userid, id.toLong()) - - return ResponseEntity.ok(block) - } - - override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unblock = accountApiService.unblock(userid, id.toLong()) - - return ResponseEntity.ok(unblock) - } - - override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unfollow = accountApiService.unfollow(userid, id.toLong()) - - return ResponseEntity.ok(unfollow) - } - - override suspend fun apiV1AccountsIdRemoveFromFollowersPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val removeFromFollowers = accountApiService.removeFromFollowers(userid, id.toLong()) - - return ResponseEntity.ok(removeFromFollowers) - } - - override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val removeFromFollowers = accountApiService.updateProfile(userid, updateCredentials) - - return ResponseEntity.ok(removeFromFollowers) - } - - override suspend fun apiV1FollowRequestsAccountIdAuthorizePost(accountId: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val acceptFollowRequest = accountApiService.acceptFollowRequest(userid, accountId.toLong()) - - return ResponseEntity.ok(acceptFollowRequest) - } - - override suspend fun apiV1FollowRequestsAccountIdRejectPost(accountId: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val rejectFollowRequest = accountApiService.rejectFollowRequest(userid, accountId.toLong()) - - return ResponseEntity.ok(rejectFollowRequest) - } - - override fun apiV1FollowRequestsGet(maxId: String?, sinceId: String?, limit: Int?): ResponseEntity> = - runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - val followRequests = accountApiService.followRequests( - userid, - false, - Page.PageByMaxId( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - limit?.coerceIn(0, 80) ?: 40 - ) - - ) - - val httpHeader = followRequests.toHttpHeader( - { "${applicationConfig.url}/api/v1/follow_requests?max_id=$it" }, - { "${applicationConfig.url}/api/v1/follow_requests?min_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(followRequests.asFlow()) - } - - ResponseEntity.ok(followRequests.asFlow()) - } - - override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val mute = accountApiService.mute(userid, id.toLong()) - - return ResponseEntity.ok(mute) - } - - override suspend fun apiV1AccountsIdUnmutePost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unmute = accountApiService.unmute(userid, id.toLong()) - - return ResponseEntity.ok(unmute) - } - - override fun apiV1MutesGet(maxId: String?, sinceId: String?, limit: Int?): ResponseEntity> = - runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - val mutes = - accountApiService.mutesAccount( - userid, - Page.PageByMaxId(maxId?.toLongOrNull(), sinceId?.toLongOrNull(), limit?.coerceIn(0, 80) ?: 40) - ) - - val httpHeader = mutes.toHttpHeader( - { "${applicationConfig.url}/api/v1/mutes?max_id=$it" }, - { "${applicationConfig.url}/api/v1/mutes?since_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(mutes.asFlow()) - } - - return@runBlocking ResponseEntity.ok(mutes.asFlow()) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt deleted file mode 100644 index 8424d6d9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.mastodon.interfaces.api.apps - -import dev.usbharu.hideout.controller.mastodon.generated.AppApi -import dev.usbharu.hideout.domain.mastodon.model.generated.Application -import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest -import dev.usbharu.hideout.mastodon.service.app.AppApiService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RequestParam - -@Controller -class MastodonAppsApiController(private val appApiService: AppApiService) : AppApi { - override suspend fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity { - return ResponseEntity( - appApiService.createApp(appsRequest), - HttpStatus.OK - ) - } - - @RequestMapping( - method = [RequestMethod.POST], - value = ["/api/v1/apps"], - produces = ["application/json"], - consumes = ["application/x-www-form-urlencoded"] - ) - suspend fun apiV1AppsPost(@RequestParam map: Map): ResponseEntity { - val appsRequest = - AppsRequest(map.getValue("client_name"), map.getValue("redirect_uris"), map["scopes"], map["website"]) - return ResponseEntity( - appApiService.createApp(appsRequest), - HttpStatus.OK - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt deleted file mode 100644 index 28f5d3df..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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.mastodon.interfaces.api.filter - -import dev.usbharu.hideout.controller.mastodon.generated.FilterApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.service.filter.MastodonFilterApiService -import kotlinx.coroutines.flow.Flow -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonFilterApiController( - private val mastodonFilterApiService: MastodonFilterApiService, - private val loginUserContextHolder: LoginUserContextHolder -) : FilterApi { - - override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteV1FilterById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV1FiltersIdGet( - id: String - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getV1FilterById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } - - override suspend fun apiV1FiltersIdPut( - id: String, - phrase: String?, - context: List?, - irreversible: Boolean?, - wholeWord: Boolean?, - expiresIn: Int? - ): ResponseEntity = super.apiV1FiltersIdPut(id, phrase, context, irreversible, wholeWord, expiresIn) - - override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.createByV1Filter(loginUserContextHolder.getLoginUserId(), v1FilterPostRequest) - ) - } - - override suspend fun apiV2FiltersFilterIdKeywordsPost( - filterId: String, - filterKeywordsPostRequest: FilterKeywordsPostRequest - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.addKeyword( - loginUserContextHolder.getLoginUserId(), - filterId.toLong(), - filterKeywordsPostRequest - ) - ) - } - - override suspend fun apiV2FiltersFilterIdStatusesPost( - filterId: String, - filterStatusRequest: FilterStatusRequest - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.addFilterStatus( - loginUserContextHolder.getLoginUserId(), - filterId.toLong(), - filterStatusRequest - ) - ) - } - - override fun apiV1FiltersGet(): ResponseEntity> = - ResponseEntity.ok(mastodonFilterApiService.v1Filters(loginUserContextHolder.getLoginUserId())) - - override fun apiV2FiltersFilterIdKeywordsGet(filterId: String): ResponseEntity> { - return ResponseEntity.ok( - mastodonFilterApiService.filterKeywords( - loginUserContextHolder.getLoginUserId(), - filterId.toLong() - ) - ) - } - - override fun apiV2FiltersFilterIdStatusesGet(filterId: String): ResponseEntity> { - return ResponseEntity.ok( - mastodonFilterApiService.filterStatuses( - loginUserContextHolder.getLoginUserId(), - filterId.toLong() - ) - ) - } - - override fun apiV2FiltersGet(): ResponseEntity> = - ResponseEntity.ok(mastodonFilterApiService.filters(loginUserContextHolder.getLoginUserId())) - - override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity = - ResponseEntity.ok(mastodonFilterApiService.getById(loginUserContextHolder.getLoginUserId(), id.toLong())) - - override suspend fun apiV2FiltersIdPut( - id: String, - title: String?, - context: List?, - filterAction: String?, - expiresIn: Int?, - keywordsAttributes: List? - ): ResponseEntity = - super.apiV2FiltersIdPut(id, title, context, filterAction, expiresIn, keywordsAttributes) - - override suspend fun apiV2FiltersKeywordsIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteKeyword(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersKeywordsIdGet(id: String): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getKeywordById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } - - override suspend fun apiV2FiltersKeywordsIdPut( - id: String, - keyword: String?, - wholeWord: Boolean?, - regex: Boolean? - ): ResponseEntity = super.apiV2FiltersKeywordsIdPut(id, keyword, wholeWord, regex) - - override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity = - ResponseEntity.ok( - mastodonFilterApiService.createFilter( - loginUserContextHolder.getLoginUserId(), - filterPostRequest - ) - ) - - override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteFilterStatusById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getFilterStatusById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt deleted file mode 100644 index adcdb770..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.mastodon.interfaces.api.media - -import dev.usbharu.hideout.controller.mastodon.generated.MediaApi -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.service.media.MediaApiService -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.multipart.MultipartFile - -@Controller -class MastodonMediaApiController(private val mediaApiService: MediaApiService) : MediaApi { - override suspend fun apiV1MediaPost( - file: MultipartFile, - thumbnail: MultipartFile?, - description: String?, - focus: String? - ): ResponseEntity { - return ResponseEntity.ok( - mediaApiService.postMedia( - MediaRequest( - file, - thumbnail, - description, - focus - ) - ) - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt deleted file mode 100644 index d9637193..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.mastodon.interfaces.api.media - -import org.springframework.web.multipart.MultipartFile - -data class MediaRequest( - val file: MultipartFile, - val thumbnail: MultipartFile?, - val description: String?, - val focus: String? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt deleted file mode 100644 index 9b0a466a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.mastodon.interfaces.api.notification - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.NotificationsApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import dev.usbharu.hideout.mastodon.service.notification.NotificationApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonNotificationApiController( - private val loginUserContextHolder: LoginUserContextHolder, - private val notificationApiService: NotificationApiService, - private val applicationConfig: ApplicationConfig -) : NotificationsApi { - override suspend fun apiV1NotificationsClearPost(): ResponseEntity { - notificationApiService.clearAll(loginUserContextHolder.getLoginUserId()) - return ResponseEntity.ok(null) - } - - override fun apiV1NotificationsGet( - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int?, - types: List?, - excludeTypes: List?, - accountId: List? - ): ResponseEntity> = runBlocking { - val notifications = notificationApiService.notifications( - loginUser = loginUserContextHolder.getLoginUserId(), - types = types.orEmpty().mapNotNull { NotificationType.parse(it) }, - excludeTypes = excludeTypes.orEmpty().mapNotNull { NotificationType.parse(it) }, - accountId = accountId.orEmpty().mapNotNull { it.toLongOrNull() }, - page = Page.of( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - minId?.toLongOrNull(), - limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = notifications.toHttpHeader( - { "${applicationConfig.url}/api/v1/notifications?min_id=$it" }, - { "${applicationConfig.url}/api/v1/notifications?max_id=$it" } - ) ?: return@runBlocking ResponseEntity.ok( - notifications.asFlow() - ) - - ResponseEntity.ok().header("Link", httpHeader).body(notifications.asFlow()) - } - - override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity { - notificationApiService.dismiss(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok(null) - } - - override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity { - val notification = notificationApiService.fingById(loginUserContextHolder.getLoginUserId(), id.toLong()) - - return ResponseEntity.ok(notification) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt deleted file mode 100644 index 20858ec6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.mastodon.interfaces.api.status - -import dev.usbharu.hideout.controller.mastodon.generated.StatusApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.service.status.StatusesApiService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonStatusesApiContoller( - private val statusesApiService: StatusesApiService, - private val loginUserContextHolder: LoginUserContextHolder -) : StatusApi { - override suspend fun apiV1StatusesPost( - devUsbharuHideoutDomainModelMastodonStatusesRequest: StatusesRequest - ): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - return ResponseEntity( - statusesApiService.postStatus( - devUsbharuHideoutDomainModelMastodonStatusesRequest, - userid - ), - HttpStatus.OK - ) - } - - override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity { - val uid = - loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(statusesApiService.removeEmojiReactions(id.toLong(), uid, emoji)) - } - - override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity { - val uid = - loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(statusesApiService.emojiReactions(id.toLong(), uid, emoji)) - } - - override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { - val uid = loginUserContextHolder.getLoginUserIdOrNull() - - return ResponseEntity.ok(statusesApiService.findById(id.toLong(), uid)) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt deleted file mode 100644 index 1005680d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.mastodon.interfaces.api.status - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequestPoll -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest.Visibility.* - -@Suppress("VariableNaming", "EnumEntryName") -class StatusesRequest { - @JsonProperty("status") - var status: String? = null - - @JsonProperty("media_ids") - var media_ids: List = emptyList() - - @JsonProperty("poll") - var poll: StatusesRequestPoll? = null - - @JsonProperty("in_reply_to_id") - var in_reply_to_id: String? = null - - @JsonProperty("sensitive") - var sensitive: Boolean? = null - - @JsonProperty("spoiler_text") - var spoiler_text: String? = null - - @JsonProperty("visibility") - var visibility: Visibility? = null - - @JsonProperty("language") - var language: String? = null - - @JsonProperty("scheduled_at") - var scheduled_at: String? = null - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is StatusesRequest) return false - - if (status != other.status) return false - if (media_ids != other.media_ids) return false - if (poll != other.poll) return false - if (in_reply_to_id != other.in_reply_to_id) return false - if (sensitive != other.sensitive) return false - if (spoiler_text != other.spoiler_text) return false - if (visibility != other.visibility) return false - if (language != other.language) return false - if (scheduled_at != other.scheduled_at) return false - - return true - } - - override fun hashCode(): Int { - var result = status?.hashCode() ?: 0 - result = 31 * result + media_ids.hashCode() - result = 31 * result + (poll?.hashCode() ?: 0) - result = 31 * result + (in_reply_to_id?.hashCode() ?: 0) - result = 31 * result + (sensitive?.hashCode() ?: 0) - result = 31 * result + (spoiler_text?.hashCode() ?: 0) - result = 31 * result + (visibility?.hashCode() ?: 0) - result = 31 * result + (language?.hashCode() ?: 0) - result = 31 * result + (scheduled_at?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "StatusesRequest(" + - "status=$status, " + - "media_ids=$media_ids, " + - "poll=$poll, " + - "in_reply_to_id=$in_reply_to_id, " + - "sensitive=$sensitive, " + - "spoiler_text=$spoiler_text, " + - "visibility=$visibility, " + - "language=$language, " + - "scheduled_at=$scheduled_at" + - ")" - } - - @Suppress("EnumNaming", "EnumEntryNameCase") - enum class Visibility { - `public`, - unlisted, - private, - direct - } -} - -fun StatusesRequest.Visibility?.toPostVisibility(): Visibility { - return when (this) { - public -> Visibility.PUBLIC - unlisted -> Visibility.UNLISTED - private -> Visibility.FOLLOWERS - direct -> Visibility.DIRECT - null -> Visibility.PUBLIC - } -} - -fun StatusesRequest.Visibility?.toStatusVisibility(): Status.Visibility { - return when (this) { - public -> Status.Visibility.public - unlisted -> Status.Visibility.unlisted - private -> Status.Visibility.private - direct -> Status.Visibility.direct - null -> Status.Visibility.public - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt deleted file mode 100644 index 7bbc8f8d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.mastodon.interfaces.api.timeline - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.TimelineApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.service.timeline.TimelineApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonTimelineApiController( - private val timelineApiService: TimelineApiService, - private val loginUserContextHolder: LoginUserContextHolder, - private val applicationConfig: ApplicationConfig, -) : TimelineApi { - override fun apiV1TimelinesHomeGet( - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int? - ): ResponseEntity> = runBlocking { - val homeTimeline = timelineApiService.homeTimeline( - userId = loginUserContextHolder.getLoginUserId(), - page = Page.of( - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = homeTimeline.toHttpHeader( - { "${applicationConfig.url}/api/v1/home?max_id=$it" }, - { "${applicationConfig.url}/api/v1/home?min_id=$it" } - ) ?: return@runBlocking ResponseEntity( - homeTimeline.asFlow(), - HttpStatus.OK - ) - ResponseEntity.ok().header("Link", httpHeader).body(homeTimeline.asFlow()) - } - - override fun apiV1TimelinesPublicGet( - local: Boolean?, - remote: Boolean?, - onlyMedia: Boolean?, - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int? - ): ResponseEntity> = runBlocking { - val publicTimeline = timelineApiService.publicTimeline( - localOnly = local ?: false, - remoteOnly = remote ?: false, - mediaOnly = onlyMedia ?: false, - page = Page.of( - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = publicTimeline.toHttpHeader( - { "${applicationConfig.url}/api/v1/public?max_id=$it" }, - { "${applicationConfig.url}/api/v1/public?min_id=$it" } - ) ?: return@runBlocking ResponseEntity( - publicTimeline.asFlow(), - HttpStatus.OK - ) - ResponseEntity.ok().header("Link", httpHeader).body(publicTimeline.asFlow()) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt deleted file mode 100644 index 0e3afc2f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ /dev/null @@ -1,379 +0,0 @@ -/* - * 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.mastodon.service.account - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.hideout.core.service.user.UpdateUserDto -import dev.usbharu.hideout.core.service.user.UserCreateDto -import dev.usbharu.hideout.core.service.user.UserService -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.domain.exception.AccountNotFoundException -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import kotlin.math.min - -@Service -@Suppress("TooManyFunctions") -interface AccountApiService { - - @Suppress("LongParameterList") - suspend fun accountsStatuses( - userid: Long, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String?, - loginUser: Long?, - page: Page - ): PaginationList - - suspend fun verifyCredentials(userid: Long): CredentialAccount - suspend fun registerAccount(userCreateDto: UserCreateDto): Unit - suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship - suspend fun account(id: Long): Account - suspend fun relationships(userid: Long, id: List, withSuspended: Boolean): List - - /** - * ブロック操作を行う - * - * @param userid ブロック操作を行ったユーザーid - * @param target ブロック対象のユーザーid - * @return ブロック後のブロック対象ユーザーとの[Relationship] - */ - suspend fun block(userid: Long, target: Long): Relationship - suspend fun unblock(userid: Long, target: Long): Relationship - suspend fun unfollow(userid: Long, target: Long): Relationship - suspend fun removeFromFollowers(userid: Long, target: Long): Relationship - suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account - - suspend fun followRequests( - loginUser: Long, - withIgnore: Boolean, - pageByMaxId: Page.PageByMaxId - ): PaginationList - - suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship - suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship - suspend fun mute(userid: Long, target: Long): Relationship - suspend fun unmute(userid: Long, target: Long): Relationship - suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList -} - -@Service -class AccountApiServiceImpl( - private val accountService: AccountService, - private val transaction: Transaction, - private val userService: UserService, - private val statusQueryService: StatusQueryService, - private val relationshipService: RelationshipService, - private val relationshipRepository: RelationshipRepository, - private val mediaService: MediaService -) : - AccountApiService { - - override suspend fun accountsStatuses( - userid: Long, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String?, - loginUser: Long?, - page: Page - ): PaginationList { - val canViewFollowers = if (loginUser == null) { - false - } else if (loginUser == userid) { - true - } else { - transaction.transaction { - isFollowing(loginUser, userid) - } - } - - return transaction.transaction { - statusQueryService.accountsStatus( - accountId = userid, - onlyMedia = onlyMedia, - excludeReplies = excludeReplies, - excludeReblogs = excludeReblogs, - pinned = pinned, - tagged = tagged, - includeFollowers = canViewFollowers, - page = page - ) - } - } - - override suspend fun verifyCredentials(userid: Long): CredentialAccount = transaction.transaction { - userService.updateUserStatistics(userid) - - val account = accountService.findById(userid) - from(account) - } - - override suspend fun registerAccount(userCreateDto: UserCreateDto) { - userService.createLocalUser(UserCreateDto(userCreateDto.name, userCreateDto.name, "", userCreateDto.password)) - } - - override suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship = transaction.transaction { - relationshipService.followRequest(loginUser, followTargetUserId) - - return@transaction fetchRelationship(loginUser, followTargetUserId) - } - - override suspend fun account(id: Long): Account { - return try { - transaction.transaction { - userService.updateUserStatistics(id) - return@transaction accountService.findById(id) - } - } catch (_: UserNotFoundException) { - throw AccountNotFoundException.ofId(id) - } - } - - override suspend fun relationships(userid: Long, id: List, withSuspended: Boolean): List = - transaction.transaction { - if (id.isEmpty()) { - return@transaction emptyList() - } - - logger.warn("id is too long! ({}) truncate to 20", id.size) - - val subList = id.subList(0, min(id.size, 20)) - - return@transaction subList.map { - fetchRelationship(userid, it) - } - } - - override suspend fun block(userid: Long, target: Long) = transaction.transaction { - relationshipService.block(userid, target) - - fetchRelationship(userid, target) - } - - override suspend fun unblock(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.unblock(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun unfollow(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.unfollow(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun removeFromFollowers(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.rejectFollowRequest(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account = - transaction.transaction { - val avatarMedia = if (updateCredentials?.avatar != null) { - mediaService.uploadLocalMedia( - MediaRequest( - updateCredentials.avatar, - null, - null, - null - ) - ) - } else { - null - } - - val headerMedia = if (updateCredentials?.header != null) { - mediaService.uploadLocalMedia( - MediaRequest( - updateCredentials.header, - null, - null, - null - ) - ) - } else { - null - } - - val account = accountService.findById(userid) - - val updateUserDto = UpdateUserDto( - screenName = updateCredentials?.displayName ?: account.displayName, - description = updateCredentials?.note ?: account.note, - avatarMedia = avatarMedia, - headerMedia = headerMedia, - locked = updateCredentials?.locked ?: account.locked, - autoAcceptFolloweeFollowRequest = false - ) - userService.updateUser(userid, updateUserDto) - - accountService.findById(userid) - } - - override suspend fun followRequests( - loginUser: Long, - withIgnore: Boolean, - pageByMaxId: Page.PageByMaxId - ): PaginationList = transaction.transaction { - val request = - relationshipRepository.findByTargetIdAndFollowRequestAndIgnoreFollowRequest( - loginUser, - true, - withIgnore, - pageByMaxId - ) - val actorIds = request.map { it.actorId } - - return@transaction PaginationList(accountService.findByIds(actorIds), request.next, request.prev) - } - - override suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship = transaction.transaction { - relationshipService.acceptFollowRequest(loginUser, target) - - return@transaction fetchRelationship(loginUser, target) - } - - override suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship = transaction.transaction { - relationshipService.rejectFollowRequest(loginUser, target) - - return@transaction fetchRelationship(loginUser, target) - } - - override suspend fun mute(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.mute(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun unmute(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.mute(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList { - val mutedAccounts = relationshipRepository.findByActorIdAndMuting(userid, true, pageByMaxId) - - return PaginationList( - accountService.findByIds(mutedAccounts.map { it.targetActorId }), - mutedAccounts.next, - mutedAccounts.prev - ) - } - - private fun from(account: Account): CredentialAccount { - return CredentialAccount( - id = account.id, - username = account.username, - acct = account.acct, - url = account.url, - displayName = account.displayName, - note = account.note, - avatar = account.avatar, - avatarStatic = account.avatarStatic, - header = account.header, - headerStatic = account.headerStatic, - locked = account.locked, - fields = account.fields, - emojis = account.emojis, - bot = account.bot, - group = account.group, - discoverable = account.discoverable, - createdAt = account.createdAt, - lastStatusAt = account.lastStatusAt, - statusesCount = account.statusesCount, - followersCount = account.followersCount, - noindex = account.noindex, - moved = account.moved, - suspendex = account.suspendex, - limited = account.limited, - followingCount = account.followingCount, - source = AccountSource( - account.note, - account.fields, - AccountSource.Privacy.public, - false, - 0 - ), - role = Role(0, "Admin", "", 32) - ) - } - - private suspend fun fetchRelationship(userid: Long, targetId: Long): Relationship { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(userid, targetId) - ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = userid, - targetActorId = targetId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userid) - ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = targetId, - targetActorId = userid, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - userService.updateUserStatistics(userid) - userService.updateUserStatistics(targetId) - - return Relationship( - id = targetId.toString(), - following = relationship.following, - showingReblogs = true, - notifying = false, - followedBy = inverseRelationship.following, - blocking = relationship.blocking, - blockedBy = inverseRelationship.blocking, - muting = relationship.muting, - mutingNotifications = relationship.muting, - requested = relationship.followRequest, - domainBlocking = false, - endorsed = false, - note = "" - ) - } - - private suspend fun isFollowing(userid: Long, target: Long): Boolean = - relationshipRepository.findByUserIdAndTargetUserId(userid, target)?.following ?: false - - companion object { - private val logger = LoggerFactory.getLogger(AccountApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt deleted file mode 100644 index e5a51f47..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.mastodon.service.account - -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.mastodon.query.AccountQueryService -import org.springframework.stereotype.Service - -@Service -interface AccountService { - suspend fun findById(id: Long): Account - suspend fun findByIds(ids: List): List -} - -@Service -class AccountServiceImpl( - private val accountQueryService: AccountQueryService -) : AccountService { - override suspend fun findById(id: Long): Account = - accountQueryService.findById(id) ?: throw IllegalArgumentException("Account $id not found.") - - override suspend fun findByIds(ids: List): List = accountQueryService.findByIds(ids) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt deleted file mode 100644 index 0f4c4157..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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.mastodon.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterAction.hide -import dev.usbharu.hideout.core.domain.model.filter.FilterAction.warn -import dev.usbharu.hideout.core.domain.model.filter.FilterMode -import dev.usbharu.hideout.core.domain.model.filter.FilterRepository -import dev.usbharu.hideout.core.domain.model.filter.FilterType.* -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import dev.usbharu.hideout.core.query.model.FilterQueryService -import dev.usbharu.hideout.core.service.filter.MuteService -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequest.FilterAction -import dev.usbharu.hideout.domain.mastodon.model.generated.V1Filter.Context -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.runBlocking -import org.springframework.stereotype.Service - -@Suppress("TooManyFunctions") -interface MastodonFilterApiService { - fun v1Filters(userId: Long): Flow - - suspend fun deleteV1FilterById(userId: Long, id: Long) - - suspend fun getV1FilterById(userId: Long, id: Long): V1Filter? - - suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter - - fun filterKeywords(userId: Long, filterId: Long): Flow - - suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword - - fun filterStatuses(userId: Long, filterId: Long): Flow - - suspend fun addFilterStatus(userId: Long, filterId: Long, filterStatusRequest: FilterStatusRequest): FilterStatus - - fun filters(userId: Long): Flow - - suspend fun deleteById(userId: Long, filterId: Long) - - suspend fun getById(userId: Long, filterId: Long): Filter? - - suspend fun deleteKeyword(userId: Long, keywordId: Long) - - suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword? - - suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter - - suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long) - - suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? -} - -@Service -class MastodonFilterApiServiceImpl( - private val muteService: MuteService, - private val filterQueryService: FilterQueryService, - private val filterRepository: FilterRepository, - private val filterKeywordRepository: FilterKeywordRepository -) : MastodonFilterApiService { - override fun v1Filters(userId: Long): Flow { - return runBlocking { filterQueryService.findByUserId(userId) }.flatMap { filterQueryModel -> - filterQueryModel.keywords.map { - V1Filter( - id = it.id.toString(), - phrase = it.keyword, - context = filterQueryModel.context.map { filterType -> - when (filterType) { - home -> Context.home - notifications -> Context.notifications - public -> Context.public - thread -> Context.thread - account -> Context.account - } - }, - expiresAt = null, - irreversible = false, - wholeWord = (it.mode != FilterMode.WHOLE_WORD).not() - ) - } - }.asFlow() - } - - override suspend fun deleteV1FilterById(userId: Long, id: Long) { - val keywordId = filterQueryService.findByUserIdAndKeywordId(userId, id)?.keywords?.singleOrNull()?.id ?: return - - filterKeywordRepository.deleteById(keywordId) - } - - override suspend fun getV1FilterById(userId: Long, id: Long): V1Filter? { - val filterQueryModel = filterQueryService.findByUserIdAndKeywordId(userId, id) ?: return null - - val filterKeyword = filterQueryModel.keywords.firstOrNull() ?: return null - - return v1Filter(filterQueryModel, filterKeyword) - } - - private fun v1Filter( - filterQueryModel: FilterQueryModel, - filterKeyword: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword - ) = V1Filter( - id = filterQueryModel.id.toString(), - phrase = filterKeyword.keyword, - context = filterQueryModel.context.map { - when (it) { - home -> Context.home - notifications -> Context.notifications - public -> Context.public - thread -> Context.thread - account -> Context.account - } - }, - expiresAt = null, - irreversible = false, - wholeWord = filterKeyword.mode == FilterMode.WHOLE_WORD - ) - - override suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter { - val createFilter = muteService.createFilter( - title = v1FilterRequest.phrase, - context = v1FilterRequest.context.map { - when (it) { - V1FilterPostRequest.Context.home -> home - V1FilterPostRequest.Context.notifications -> notifications - V1FilterPostRequest.Context.public -> public - V1FilterPostRequest.Context.thread -> thread - V1FilterPostRequest.Context.account -> account - } - }, - action = warn, - keywords = listOf( - dev.usbharu.hideout.core.service.filter.FilterKeyword( - v1FilterRequest.phrase, - if (v1FilterRequest.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - ), - loginUser = userId - ) - - return v1Filter(createFilter, createFilter.keywords.first()) - } - - override fun filterKeywords(userId: Long, filterId: Long): Flow = - runBlocking { filterQueryService.findByUserIdAndId(userId, filterId) } - ?.keywords - ?.map { - toFilterKeyword( - it - ) - } - .orEmpty() - .asFlow() - - override suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword { - val id = filterQueryService.findByUserIdAndId(userId, filterId)?.id - ?: throw IllegalArgumentException("filter not found.") - - val filterKeyword = filterKeywordRepository.save( - dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword( - id = filterKeywordRepository.generateId(), - filterId = id, - keyword = keyword.keyword, - mode = if (keyword.regex == true) { - FilterMode.REGEX - } else if (keyword.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - ) - - return toFilterKeyword(filterKeyword) - } - - override fun filterStatuses(userId: Long, filterId: Long): Flow = emptyFlow() - - override suspend fun addFilterStatus( - userId: Long, - filterId: Long, - filterStatusRequest: FilterStatusRequest - ): FilterStatus { - TODO() - } - - override fun filters(userId: Long): Flow = - runBlocking { filterQueryService.findByUserId(userId) }.map { filterQueryModel -> - toFilter(filterQueryModel) - }.asFlow() - - private fun toFilter(filterQueryModel: FilterQueryModel) = Filter( - id = filterQueryModel.id.toString(), - title = filterQueryModel.name, - context = filterQueryModel.context.map { - when (it) { - home -> Filter.Context.home - notifications -> Filter.Context.notifications - public -> Filter.Context.public - thread -> Filter.Context.thread - account -> Filter.Context.account - } - }, - expiresAt = null, - filterAction = when (filterQueryModel.filterAction) { - warn -> Filter.FilterAction.warn - hide -> Filter.FilterAction.hide - }, - keywords = filterQueryModel.keywords.map { - toFilterKeyword(it) - }, - statuses = null - ) - - private fun toFilterKeyword(it: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword) = FilterKeyword( - it.id.toString(), - it.keyword, - it.mode == FilterMode.WHOLE_WORD - ) - - override suspend fun deleteById(userId: Long, filterId: Long) = - filterRepository.deleteByUserIdAndId(userId, filterId) - - override suspend fun getById(userId: Long, filterId: Long): Filter? = - filterQueryService.findByUserIdAndId(userId, filterId)?.let { toFilter(it) } - - override suspend fun deleteKeyword(userId: Long, keywordId: Long) { - val id = filterQueryService.findByUserIdAndKeywordId(userId, keywordId)?.keywords?.singleOrNull()?.id ?: return - - filterKeywordRepository.deleteById(id) - } - - override suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword? { - return filterQueryService - .findByUserIdAndKeywordId(userId, keywordId) - ?.keywords - ?.firstOrNull() - ?.let { toFilterKeyword(it) } - } - - override suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter { - val keywords = filterPostRequest.keywordsAttributes.orEmpty().map { - dev.usbharu.hideout.core.service.filter.FilterKeyword( - it.keyword, - if (it.regex == true) { - FilterMode.REGEX - } else if (it.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - } - return toFilter( - muteService.createFilter( - title = filterPostRequest.title, - context = filterPostRequest.context.map { - when (it) { - FilterPostRequest.Context.home -> home - FilterPostRequest.Context.notifications -> notifications - FilterPostRequest.Context.public -> public - FilterPostRequest.Context.thread -> thread - FilterPostRequest.Context.account -> account - } - }, - action = when (filterPostRequest.filterAction) { - FilterAction.warn -> warn - FilterAction.hide -> warn - null -> warn - }, - keywords = keywords, - loginUser = userId - ) - ) - } - - override suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long) = Unit - - override suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? = null -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt deleted file mode 100644 index d24fe791..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.mastodon.service.instance - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import org.springframework.stereotype.Service - -@Service -interface InstanceApiService { - suspend fun v1Instance(): V1Instance -} - -@Service -class InstanceApiServiceImpl(private val applicationConfig: ApplicationConfig) : InstanceApiService { - @Suppress("LongMethod") - override suspend fun v1Instance(): V1Instance { - val url = applicationConfig.url - return V1Instance( - uri = url.host, - title = "Hideout Server", - shortDescription = "Hideout test server", - description = "This server is operated for testing of Hideout." + - " We are not responsible for any events that occur when associating with this server", - email = "i@usbharu.dev", - version = "0.0.1", - urls = V1InstanceUrls("wss://${url.host}"), - stats = V1InstanceStats(1, 0, 0), - thumbnail = null, - languages = listOf("ja-JP"), - registrations = false, - approvalRequired = false, - invitesEnabled = false, - configuration = V1InstanceConfiguration( - accounts = V1InstanceConfigurationAccounts(1), - statuses = V1InstanceConfigurationStatuses( - 300, - 4, - 23 - ), - mediaAttachments = V1InstanceConfigurationMediaAttachments( - emptyList(), - 0, - 0, - 0, - 0 - ), - polls = V1InstanceConfigurationPolls( - 0, - 0, - 0, - 0 - ) - ), - contactAccount = Account( - id = "0", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = false, - createdAt = "0", - lastStatusAt = "0", - statusesCount = 1, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - rules = emptyList() - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt deleted file mode 100644 index 8e806d4d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.mastodon.service.media - -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import org.springframework.stereotype.Service - -@Service -interface MediaApiService { - suspend fun postMedia(mediaRequest: MediaRequest): MediaAttachment -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt deleted file mode 100644 index 05f70e8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.mastodon.service.media - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import org.springframework.stereotype.Service - -@Service -class MediaApiServiceImpl(private val mediaService: MediaService, private val transaction: Transaction) : - MediaApiService { - - override suspend fun postMedia(mediaRequest: MediaRequest): MediaAttachment { - return transaction.transaction { - val uploadLocalMedia = mediaService.uploadLocalMedia(mediaRequest) - val type = when (uploadLocalMedia.type) { - FileType.Image -> MediaAttachment.Type.image - FileType.Video -> MediaAttachment.Type.video - FileType.Audio -> MediaAttachment.Type.audio - FileType.Unknown -> MediaAttachment.Type.unknown - } - return@transaction MediaAttachment( - id = uploadLocalMedia.id.toString(), - type = type, - url = uploadLocalMedia.url, - previewUrl = uploadLocalMedia.thumbnailUrl, - description = mediaRequest.description, - blurhash = uploadLocalMedia.blurHash, - textUrl = uploadLocalMedia.url - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt deleted file mode 100644 index d632e04c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.mastodon.service.notification - -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.reaction.Reaction -import dev.usbharu.hideout.core.service.notification.NotificationStore -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component - -@Component -class MastodonNotificationStore(private val mastodonNotificationRepository: MastodonNotificationRepository) : - NotificationStore { - override suspend fun publishNotification( - notification: Notification, - user: Actor, - sourceActor: Actor?, - post: Post?, - reaction: Reaction? - ): Boolean { - val notificationType = when (notification.type) { - "mention" -> NotificationType.mention - "post" -> NotificationType.status - "repost" -> NotificationType.reblog - "follow" -> NotificationType.follow - "follow-request" -> NotificationType.follow_request - "reaction" -> NotificationType.favourite - else -> { - logger.debug("Notification type does not support. type: {}", notification.type) - return false - } - } - - if (notification.sourceActorId == null) { - logger.debug("Notification does not support. notification.sourceActorId is null") - return false - } - - val mastodonNotification = MastodonNotification( - id = notification.id, - userId = notification.userId, - type = notificationType, - createdAt = notification.createdAt, - accountId = notification.sourceActorId, - statusId = notification.postId, - reportId = null, - relationshipServeranceEvent = null - ) - - mastodonNotificationRepository.save(mastodonNotification) - - return true - } - - override suspend fun unpulishNotification(notificationId: Long): Boolean { - mastodonNotificationRepository.deleteById(notificationId) - return true - } - - companion object { - private val logger = LoggerFactory.getLogger(MastodonNotificationStore::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt deleted file mode 100644 index 32c762c6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.mastodon.service.notification - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.NotificationType - -interface NotificationApiService { - - suspend fun notifications( - loginUser: Long, - types: List, - excludeTypes: List, - accountId: List, - page: Page - ): PaginationList - - suspend fun fingById(loginUser: Long, notificationId: Long): Notification? - - suspend fun clearAll(loginUser: Long) - - suspend fun dismiss(loginUser: Long, notificationId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt deleted file mode 100644 index 52caed8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.mastodon.service.notification - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import dev.usbharu.hideout.mastodon.domain.model.NotificationType.* -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import dev.usbharu.hideout.mastodon.service.account.AccountService -import org.springframework.stereotype.Service - -@Service -class NotificationApiServiceImpl( - private val mastodonNotificationRepository: MastodonNotificationRepository, - private val transaction: Transaction, - private val accountService: AccountService, - private val statusQueryService: StatusQueryService -) : - NotificationApiService { - - override suspend fun notifications( - loginUser: Long, - types: List, - excludeTypes: List, - accountId: List, - page: Page - ): PaginationList = transaction.transaction { - val typesTmp = mutableListOf() - - typesTmp.addAll(types) - typesTmp.removeAll(excludeTypes) - - val mastodonNotifications = - mastodonNotificationRepository.findByUserIdAndInTypesAndInSourceActorId( - loginUser, - typesTmp, - accountId, - page - ) - - val accounts = accountService.findByIds( - mastodonNotifications.map { - it.accountId - } - ).associateBy { it.id.toLong() } - - val statuses = statusQueryService.findByPostIds(mastodonNotifications.mapNotNull { it.statusId }) - .associateBy { it.id.toLong() } - - val notifications = mastodonNotifications.map { - Notification( - id = it.id.toString(), - type = convertNotificationType(it.type), - createdAt = it.createdAt.toString(), - account = accounts.getValue(it.accountId), - status = statuses[it.statusId], - report = null, - relationshipSeveranceEvent = null - ) - } - - return@transaction PaginationList(notifications, mastodonNotifications.next, mastodonNotifications.prev) - } - - override suspend fun fingById(loginUser: Long, notificationId: Long): Notification? { - val findById = mastodonNotificationRepository.findById(notificationId) ?: return null - - if (findById.userId != loginUser) { - return null - } - - val account = accountService.findById(findById.accountId) - val status = findById.statusId?.let { statusQueryService.findByPostId(it) } - - return Notification( - id = findById.id.toString(), - type = convertNotificationType(findById.type), - createdAt = findById.createdAt.toString(), - account = account, - status = status, - report = null, - relationshipSeveranceEvent = null - ) - } - - override suspend fun clearAll(loginUser: Long) { - mastodonNotificationRepository.deleteByUserId(loginUser) - } - - override suspend fun dismiss(loginUser: Long, notificationId: Long) { - mastodonNotificationRepository.deleteByUserIdAndId(loginUser, notificationId) - } - - private fun convertNotificationType(notificationType: NotificationType): Notification.Type = - when (notificationType) { - mention -> Notification.Type.mention - status -> Notification.Type.status - reblog -> Notification.Type.reblog - follow -> Notification.Type.follow - follow_request -> Notification.Type.follow - favourite -> Notification.Type.follow_request - poll -> Notification.Type.poll - update -> Notification.Type.update - admin_sign_up -> Notification.Type.adminPeriodSign_up - admin_report -> Notification.Type.adminPeriodReport - severed_relationships -> Notification.Type.severed_relationships - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt deleted file mode 100644 index 07ae96c8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.mastodon.service.status - -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.domain.model.media.MediaRepository -import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.post.PostCreateDto -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.reaction.ReactionService -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.Status.Visibility.* -import dev.usbharu.hideout.mastodon.domain.exception.StatusNotFoundException -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest -import dev.usbharu.hideout.mastodon.interfaces.api.status.toPostVisibility -import dev.usbharu.hideout.mastodon.interfaces.api.status.toStatusVisibility -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import dev.usbharu.hideout.mastodon.service.account.AccountService -import dev.usbharu.hideout.util.EmojiUtil -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -interface StatusesApiService { - suspend fun postStatus( - statusesRequest: StatusesRequest, - userId: Long, - ): Status - - suspend fun findById( - id: Long, - userId: Long?, - ): Status - - suspend fun emojiReactions( - postId: Long, - userId: Long, - emojiName: String, - ): Status - - suspend fun removeEmojiReactions( - postId: Long, - userId: Long, - emojiName: String, - ): Status -} - -@Service -@Suppress("LongParameterList") -class StatsesApiServiceImpl( - private val postService: PostService, - private val accountService: AccountService, - private val mediaRepository: MediaRepository, - private val transaction: Transaction, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val statusQueryService: StatusQueryService, - private val relationshipRepository: RelationshipRepository, - private val reactionService: ReactionService, - private val emojiService: EmojiService, -) : - StatusesApiService { - override suspend fun postStatus( - statusesRequest: StatusesRequest, - userId: Long, - ): Status = transaction.transaction { - logger.debug("START create post by mastodon api. {}", statusesRequest) - - val post = postService.createLocal( - PostCreateDto( - text = statusesRequest.status.orEmpty(), - overview = statusesRequest.spoiler_text, - visibility = statusesRequest.visibility.toPostVisibility(), - repolyId = statusesRequest.in_reply_to_id?.toLong(), - userId = userId, - mediaIds = statusesRequest.media_ids.map { it.toLong() } - ) - ) - val account = accountService.findById(userId) - - val replyUser = if (post.replyId != null) { - val findById = postRepository.findById(post.replyId) - if (findById == null) { - null - } else { - actorRepository.findById(findById.actorId)?.id - } - } else { - null - } - - // TODO: n+1解消 - val mediaAttachment = post.mediaIds.mapNotNull { mediaId -> - mediaRepository.findById(mediaId) - }.map { - it.toMediaAttachments() - } - - Status( - id = post.id.toString(), - uri = post.apId, - createdAt = Instant.ofEpochMilli(post.createdAt).toString(), - account = account, - content = post.text, - visibility = statusesRequest.visibility.toStatusVisibility(), - sensitive = post.sensitive, - spoilerText = post.overview.orEmpty(), - mediaAttachments = mediaAttachment, - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = post.url, - inReplyToId = post.replyId?.toString(), - inReplyToAccountId = replyUser?.toString(), - language = null, - text = post.text, - editedAt = null, - ) - } - - override suspend fun findById(id: Long, userId: Long?): Status = transaction.transaction { - val status = statusQueryService.findByPostId(id) ?: statusNotFound(id) - - return@transaction status(status, userId) - } - - private fun accessDenied(id: String): Nothing { - logger.debug("Access Denied $id") - throw StatusNotFoundException.ofId(id.toLong()) - } - - private fun statusNotFound(id: Long): Nothing { - logger.debug("Status Not Found $id") - throw StatusNotFoundException.ofId(id) - } - - private suspend fun status( - status: Status, - userId: Long?, - ): Status { - return when (status.visibility) { - public -> status - unlisted -> status - private -> { - if (userId == null) { - accessDenied(status.id) - } - - val relationship = - relationshipRepository.findByUserIdAndTargetUserId(userId, status.account.id.toLong()) - ?: accessDenied(status.id) - if (relationship.following) { - return status - } - accessDenied(status.id) - } - - direct -> accessDenied(status.id) - } - } - - override suspend fun emojiReactions(postId: Long, userId: Long, emojiName: String): Status { - status(statusQueryService.findByPostId(postId) ?: statusNotFound(postId), userId) - - val emoji = try { - if (EmojiUtil.isEmoji(emojiName)) { - UnicodeEmoji(emojiName) - } else { - emojiService.findByEmojiName(emojiName)!! - } - } catch (_: IllegalStateException) { - UnicodeEmoji("❤") - } catch (_: NullPointerException) { - UnicodeEmoji("❤") - } - reactionService.sendReaction(emoji, userId, postId) - return statusQueryService.findByPostId(postId) ?: statusNotFound(postId) - } - - override suspend fun removeEmojiReactions(postId: Long, userId: Long, emojiName: String): Status { - reactionService.removeReaction(userId, postId) - - return status(statusQueryService.findByPostId(postId) ?: statusNotFound(postId), userId) - } - - companion object { - private val logger = LoggerFactory.getLogger(StatusesApiService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt deleted file mode 100644 index 3bfd728d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.mastodon.service.timeline - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.service.timeline.GenerateTimelineService -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import org.springframework.stereotype.Service - -@Suppress("LongParameterList") -interface TimelineApiService { - - suspend fun publicTimeline( - localOnly: Boolean = false, - remoteOnly: Boolean = false, - mediaOnly: Boolean = false, - page: Page - ): PaginationList - - suspend fun homeTimeline( - userId: Long, - page: Page - ): PaginationList -} - -@Service -class TimelineApiServiceImpl( - private val generateTimelineService: GenerateTimelineService, - private val transaction: Transaction -) : TimelineApiService { - - override suspend fun publicTimeline( - localOnly: Boolean, - remoteOnly: Boolean, - mediaOnly: Boolean, - page: Page - ): PaginationList = transaction.transaction { - return@transaction generateTimelineService.getTimeline(forUserId = 0, localOnly, mediaOnly, page) - } - - override suspend fun homeTimeline(userId: Long, page: Page): PaginationList = - transaction.transaction { - return@transaction generateTimelineService.getTimeline(forUserId = userId, page = page) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt deleted file mode 100644 index 36c8f322..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.util - -import dev.usbharu.hideout.core.domain.model.actor.Acct - -object AcctUtil { - fun parse(string: String): Acct { - if (string.isBlank()) { - throw IllegalArgumentException("Invalid acct.(Blank)") - } - return when (string.count { c -> c == '@' }) { - 0 -> { - Acct(string) - } - - 1 -> { - if (string.startsWith("@")) { - Acct(string.substring(1 until string.length)) - } else { - Acct(string.substringBefore("@"), string.substringAfter("@")) - } - } - - 2 -> { - if (string.startsWith("@")) { - val substring = string.substring(1 until string.length) - val userName = substring.substringBefore("@") - val domain = substring.substringAfter("@") - Acct( - userName, - domain.ifBlank { null } - ) - } else { - throw IllegalArgumentException("Invalid acct.(@ are in the wrong position)") - } - } - - else -> { - throw IllegalArgumentException("Invalid acct. (Too many @)") - } - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt deleted file mode 100644 index 01fd3842..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.util - -import io.ktor.http.* - -object HttpUtil { - val Activity: ContentType - get() = ContentType("application", "activity+json") - - val JsonLd: ContentType - get() { - return ContentType( - contentType = "application", - contentSubtype = "ld+json", - parameters = listOf(HeaderValueParam("profile", "https://www.w3.org/ns/activitystreams")) - ) - } - - fun isContentTypeOfActivityPub( - contentType: String, - subType: String - ): Boolean { - if (contentType != "application") { - return false - } - if (subType == "activity+json") { - return true - } - return subType == "ld+json" - } - - fun isContentTypeOfActivityPub(contentType: ContentType): Boolean { - return isContentTypeOfActivityPub( - contentType.contentType, - contentType.contentSubtype - ) - } -// fun -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/LruCache.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/LruCache.kt deleted file mode 100644 index c5ffdea2..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/LruCache.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.util - -import java.io.Serial - -class LruCache(private val maxSize: Int) : LinkedHashMap(15, 0.75f, true) { - - override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean = size > maxSize - override fun toString(): String { - return "LruCache(" + - "maxSize=$maxSize" + - ")" + - " ${super.toString()}" - } - - companion object { - @Serial - private const val serialVersionUID: Long = -6446947260925053191L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt index 8827b61a..56b20ac3 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt @@ -44,10 +44,4 @@ object RsaUtil { } fun decodeRsaPrivateKey(encoded: String): RSAPrivateKey = decodeRsaPrivateKey(Base64Util.decode(encoded)) - - fun decodeRsaPrivateKeyPem(pem: String): RSAPrivateKey { - val replace = pem.replace(replaceHeaderAndFooterRegex, "") - .replace("\n", "") - return decodeRsaPrivateKey(replace) - } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/ServerUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/ServerUtil.kt deleted file mode 100644 index 137d449a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/ServerUtil.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.util - -object ServerUtil { - fun getImplementationVersion(): String = - ServerUtil.javaClass.`package`.implementationVersion ?: "DEVELOPMENT-VERSION" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt deleted file mode 100644 index 39fcda52..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.util - -import java.nio.file.Files -import java.nio.file.Path - -fun T.withDelete(): TempFile = TempFile(this) - -class TempFile(val path: T) : AutoCloseable { - override fun close() { - path?.let { Files.deleteIfExists(it) } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TempFile<*> - - return path == other.path - } - - override fun hashCode(): Int = path?.hashCode() ?: 0 -} diff --git a/hideout-core/src/main/resources/application.yml b/hideout-core/src/main/resources/application.yml index 52bc6eba..55cec25b 100644 --- a/hideout-core/src/main/resources/application.yml +++ b/hideout-core/src/main/resources/application.yml @@ -1,3 +1,4 @@ +#file: noinspection SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection hideout: url: "https://test-hideout-dev.usbharu.dev" use-mongodb: true @@ -23,7 +24,7 @@ spring: default-property-inclusion: always datasource: driver-class-name: org.postgresql.Driver - url: "jdbc:postgresql:hideout2" + url: "jdbc:postgresql:hideout3" username: "postgres" password: "" data: diff --git a/hideout-core/src/main/resources/db/migration/V1707799249__Filter.sql b/hideout-core/src/main/resources/db/migration/V1707799249__Filter.sql deleted file mode 100644 index 3b05b856..00000000 --- a/hideout-core/src/main/resources/db/migration/V1707799249__Filter.sql +++ /dev/null @@ -1,18 +0,0 @@ -create table if not exists filters -( - id bigint primary key not null, - user_id bigint not null, - name varchar(255) not null, - context varchar(500) not null, - action varchar(255) not null, - constraint fk_filters_user_id__id foreign key (user_id) references actors (id) on delete cascade on update cascade -); - -create table if not exists filter_keywords -( - id bigint primary key not null, - filter_id bigint not null, - keyword varchar(1000) not null, - mode varchar(100) not null, - constraint fk_filter_keywords_filter_id__id foreign key (filter_id) references filters (id) on delete cascade on update cascade -); \ No newline at end of file diff --git a/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql b/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql index 09f4f0ca..fdc8afea 100644 --- a/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql +++ b/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql @@ -41,19 +41,32 @@ create table if not exists actors url varchar(1000) not null unique, public_key varchar(10000) not null, private_key varchar(10000) null, - created_at bigint not null, + created_at timestamp not null, key_id varchar(1000) not null, "following" varchar(1000) null, followers varchar(1000) null, - "instance" bigint not null, + "instance" bigint not null, locked boolean not null, - following_count int not null, - followers_count int not null, + following_count int null, + followers_count int null, posts_count int not null, last_post_at timestamp null default null, - emojis varchar(300) not null default '', + last_update_at timestamp not null, + suspend boolean not null, + move_to bigint null default null, + emojis varchar(3000) not null default '', + deleted boolean not null default false, unique ("name", "domain"), - constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict + constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict, + constraint fk_actors_actors__move_to foreign key ("move_to") references actors (id) on delete restrict on update restrict +); + +create table if not exists actor_alsoknownas +( + actor_id bigint not null, + also_known_as bigint not null, + constraint fk_actor_alsoknownas_actors__actor_id foreign key ("actor_id") references actors (id) on delete cascade on update cascade, + constraint fk_actor_alsoknownas_actors__also_known_as foreign key ("also_known_as") references actors (id) on delete cascade on update cascade ); create table if not exists user_details @@ -62,6 +75,7 @@ create table if not exists user_details actor_id bigint not null unique, password varchar(255) not null, auto_accept_followee_follow_request boolean not null, + last_migration timestamp null default null, constraint fk_user_details_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict ); @@ -72,19 +86,11 @@ create table if not exists media url varchar(255) not null unique, remote_url varchar(255) null unique, thumbnail_url varchar(255) null unique, - "type" int not null, + "type" varchar(100) not null, blurhash varchar(255) null, mime_type varchar(255) not null, description varchar(4000) null ); -create table if not exists meta_info -( - id bigint primary key, - version varchar(1000) not null, - kid varchar(1000) not null, - jwt_private_key varchar(100000) not null, - jwt_public_key varchar(100000) not null -); create table if not exists posts ( id bigint primary key, @@ -92,14 +98,16 @@ create table if not exists posts overview varchar(100) null, content varchar(5000) not null, text varchar(3000) not null, - created_at bigint not null, - visibility int default 0 not null, + created_at timestamp not null, + visibility varchar(100) not null, url varchar(500) not null, repost_id bigint null, reply_id bigint null, "sensitive" boolean default false not null, ap_id varchar(100) not null unique, - deleted boolean default false not null + deleted boolean default false not null, + hide boolean default false not null, + move_to bigint default null null ); alter table posts add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict; @@ -107,6 +115,9 @@ alter table posts add constraint fk_posts_repostid__id foreign key (repost_id) references posts (id) on delete restrict on update restrict; alter table posts add constraint fk_posts_replyid__id foreign key (reply_id) references posts (id) on delete restrict on update restrict; +alter table posts + add constraint fk_posts_move_to__id foreign key (move_to) references posts (id) on delete CASCADE on update cascade; + create table if not exists posts_media ( post_id bigint, @@ -130,100 +141,19 @@ alter table posts_emojis alter table posts_emojis add constraint fk_posts_emojis_emoji_id__id foreign key (emoji_id) references emojis (id) on delete cascade on update cascade; -create table if not exists reactions -( - id bigint primary key, - unicode_emoji varchar(255) null default null, - custom_emoji_id bigint null default null, - post_id bigint not null, - actor_id bigint not null, - unique (post_id, actor_id) -); -alter table reactions - add constraint fk_reactions_post_id__id foreign key (post_id) references posts (id) on delete restrict on update restrict; -alter table reactions - add constraint fk_reactions_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict; -alter table reactions - add constraint fk_reactions_custom_emoji_id__id foreign key (custom_emoji_id) references emojis (id) on delete cascade on update cascade; -create table if not exists timelines +create table if not exists posts_visible_actors ( - id bigint primary key, - user_id bigint not null, - timeline_id bigint not null, - post_id bigint not null, - post_actor_id bigint not null, - created_at bigint not null, - reply_id bigint null, - repost_id bigint null, - visibility int not null, - "sensitive" boolean not null, - is_local boolean not null, - is_pure_repost boolean not null, - media_ids varchar(255) not null, - emoji_ids varchar(255) not null + post_id bigint not null, + actor_id bigint not null, + constraint pk_postsvisibleactors primary key (post_id, actor_id) ); -create table if not exists application_authorization -( - id varchar(255) primary key, - registered_client_id varchar(255) not null, - principal_name varchar(255) not null, - authorization_grant_type varchar(255) not null, - authorized_scopes varchar(1000) default null null, - "attributes" varchar(4000) default null null, - "state" varchar(500) default null null, - authorization_code_value varchar(4000) default null null, - authorization_code_issued_at timestamp default null null, - authorization_code_expires_at timestamp default null null, - authorization_code_metadata varchar(2000) default null null, - access_token_value varchar(4000) default null null, - access_token_issued_at timestamp default null null, - access_token_expires_at timestamp default null null, - access_token_metadata varchar(2000) default null null, - access_token_type varchar(255) default null null, - access_token_scopes varchar(1000) default null null, - refresh_token_value varchar(4000) default null null, - refresh_token_issued_at timestamp default null null, - refresh_token_expires_at timestamp default null null, - refresh_token_metadata varchar(2000) default null null, - oidc_id_token_value varchar(4000) default null null, - oidc_id_token_issued_at timestamp default null null, - oidc_id_token_expires_at timestamp default null null, - oidc_id_token_metadata varchar(2000) default null null, - oidc_id_token_claims varchar(2000) default null null, - user_code_value varchar(4000) default null null, - user_code_issued_at timestamp default null null, - user_code_expires_at timestamp default null null, - user_code_metadata varchar(2000) default null null, - device_code_value varchar(4000) default null null, - device_code_issued_at timestamp default null null, - device_code_expires_at timestamp default null null, - device_code_metadata varchar(2000) default null null -); -create table if not exists oauth2_authorization_consent -( - registered_client_id varchar(100), - principal_name varchar(200), - authorities varchar(1000) not null, - constraint pk_oauth2_authorization_consent primary key (registered_client_id, principal_name) -); -create table if not exists registered_client -( - id varchar(100) primary key, - client_id varchar(100) not null, - client_id_issued_at timestamp default current_timestamp not null, - client_secret varchar(200) default null null, - client_secret_expires_at timestamp default null null, - client_name varchar(200) not null, - client_authentication_methods varchar(1000) not null, - authorization_grant_types varchar(1000) not null, - redirect_uris varchar(1000) default null null, - post_logout_redirect_uris varchar(1000) default null null, - scopes varchar(1000) not null, - client_settings varchar(2000) not null, - token_settings varchar(2000) not null -); +alter table posts_visible_actors + add constraint fk_posts_visible_actors_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade; +alter table posts_visible_actors + add constraint fk_posts_visible_actors_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade; + create table if not exists relationships ( @@ -246,43 +176,88 @@ values (0, 'system', '', '', '', null, '', '', false, false, '', current_timesta insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, key_id, following, followers, instance, locked, following_count, followers_count, posts_count, - last_post_at) -values (0, 'ghost', '', '', '', '', '', '', '', null, 0, '', '', '', 0, true, 0, 0, 0, null); + last_post_at, last_update_at, suspend, move_to, emojis) +values (0, '', '', '', '', '', '', '', '', null, current_timestamp, '', null, null, 0, true, null, null, 0, null, + current_timestamp, false, null, ''); -create table if not exists deleted_actors +create table if not exists applications ( - id bigint primary key, - "name" varchar(300) not null, - domain varchar(255) not null, - public_key varchar(10000) not null, - deleted_at timestamp not null, - unique ("name", domain) + id bigint primary key, + name varchar(500) not null ); -create table if not exists notifications +create table if not exists oauth2_registered_client ( - id bigint primary key, - type varchar(100) not null, - user_id bigint not null, - source_actor_id bigint null, - post_id bigint null, - text varchar(3000) null, - reaction_id bigint null, - created_at timestamp not null, - constraint fk_notifications_user_id__id foreign key (user_id) references actors (id) on delete cascade on update cascade, - constraint fk_notifications_source_actor__id foreign key (source_actor_id) references actors (id) on delete cascade on update cascade, - constraint fk_notifications_post_id__id foreign key (post_id) references posts (id) on delete cascade on update cascade, - constraint fk_notifications_reaction_id__id foreign key (reaction_id) references reactions (id) on delete cascade on update cascade + id varchar(100) NOT NULL, + client_id varchar(100) NOT NULL, + client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, + client_secret varchar(200) DEFAULT NULL, + client_secret_expires_at timestamp DEFAULT NULL, + client_name varchar(200) NOT NULL, + client_authentication_methods varchar(1000) NOT NULL, + authorization_grant_types varchar(1000) NOT NULL, + redirect_uris varchar(1000) DEFAULT NULL, + post_logout_redirect_uris varchar(1000) DEFAULT NULL, + scopes varchar(1000) NOT NULL, + client_settings varchar(2000) NOT NULL, + token_settings varchar(2000) NOT NULL, + PRIMARY KEY (id) ); -create table if not exists mastodon_notifications +CREATE TABLE if not exists oauth2_authorization_consent ( - id bigint primary key, - user_id bigint not null, - type varchar(100) not null, - created_at timestamp not null, - account_id bigint not null, - status_id bigint null, - report_id bigint null, - relationship_serverance_event_id bigint null -) \ No newline at end of file + registered_client_id varchar(100) NOT NULL, + principal_name varchar(200) NOT NULL, + authorities varchar(1000) NOT NULL, + PRIMARY KEY (registered_client_id, principal_name) +); + +CREATE TABLE oauth2_authorization +( + id varchar(100) NOT NULL, + registered_client_id varchar(100) NOT NULL, + principal_name varchar(200) NOT NULL, + authorization_grant_type varchar(100) NOT NULL, + authorized_scopes varchar(1000) DEFAULT NULL, + attributes varchar(4000) DEFAULT NULL, + state varchar(500) DEFAULT NULL, + authorization_code_value varchar(4000) DEFAULT NULL, + authorization_code_issued_at timestamp DEFAULT NULL, + authorization_code_expires_at timestamp DEFAULT NULL, + authorization_code_metadata varchar(4000) DEFAULT NULL, + access_token_value varchar(4000) DEFAULT NULL, + access_token_issued_at timestamp DEFAULT NULL, + access_token_expires_at timestamp DEFAULT NULL, + access_token_metadata varchar(4000) DEFAULT NULL, + access_token_type varchar(100) DEFAULT NULL, + access_token_scopes varchar(1000) DEFAULT NULL, + oidc_id_token_value varchar(4000) DEFAULT NULL, + oidc_id_token_issued_at timestamp DEFAULT NULL, + oidc_id_token_expires_at timestamp DEFAULT NULL, + oidc_id_token_metadata varchar(4000) DEFAULT NULL, + refresh_token_value varchar(4000) DEFAULT NULL, + refresh_token_issued_at timestamp DEFAULT NULL, + refresh_token_expires_at timestamp DEFAULT NULL, + refresh_token_metadata varchar(4000) DEFAULT NULL, + user_code_value varchar(4000) DEFAULT NULL, + user_code_issued_at timestamp DEFAULT NULL, + user_code_expires_at timestamp DEFAULT NULL, + user_code_metadata varchar(4000) DEFAULT NULL, + device_code_value varchar(4000) DEFAULT NULL, + device_code_issued_at timestamp DEFAULT NULL, + device_code_expires_at timestamp DEFAULT NULL, + device_code_metadata varchar(4000) DEFAULT NULL, + PRIMARY KEY (id) +); + +create table if not exists actor_instance_relationships +( + actor_id bigint not null, + instance_id bigint not null, + blocking boolean not null, + muting boolean not null, + do_not_send_private boolean not null, + PRIMARY KEY (actor_id, instance_id), + constraint fk_actor_instance_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade, + constraint fk_actor_instance_relationships_instance_id__id foreign key (instance_id) references instance (id) on delete cascade on update cascade +); \ No newline at end of file diff --git a/hideout-core/src/main/resources/log4j2.xml b/hideout-core/src/main/resources/log4j2.xml index 7c9b917d..195006c3 100644 --- a/hideout-core/src/main/resources/log4j2.xml +++ b/hideout-core/src/main/resources/log4j2.xml @@ -6,9 +6,11 @@ - + + + \ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/sign_up.html b/hideout-core/src/main/resources/templates/sign_up.html index 3408bb8a..63f57c5a 100644 --- a/hideout-core/src/main/resources/templates/sign_up.html +++ b/hideout-core/src/main/resources/templates/sign_up.html @@ -3,23 +3,12 @@ SignUp - - -
+ -
diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt index b25ec4a2..96f3655f 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/EqualsAndToStringTest.kt @@ -124,7 +124,7 @@ class EqualsAndToStringTest { } try { ToStringVerifier.forClass(it).withPreset(Presets.INTELLI_J).verify() - } catch (e: Exception) { + } catch (e: Throwable) { e.printStackTrace() } } diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/AnnounceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/AnnounceTest.kt deleted file mode 100644 index ed037bd6..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/AnnounceTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.junit.jupiter.api.Test - -class AnnounceTest{ - @Test - fun mastodonのjsonをデシリアライズできる() { - //language=JSON - val json = """{ - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://kb.usbharu.dev/users/usbharu/statuses/111859915842276344/activity", - "type": "Announce", - "actor": "https://kb.usbharu.dev/users/usbharu", - "published": "2024-02-02T04:07:40Z", - "to": [ - "https://kb.usbharu.dev/users/usbharu/followers" - ], - "cc": [ - "https://kb.usbharu.dev/users/usbharu" - ], - "object": "https://kb.usbharu.dev/users/usbharu/statuses/111850484548963326" -}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/CreateTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/CreateTest.kt deleted file mode 100644 index ce633497..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/CreateTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Test - -class CreateTest { - @Test - fun Createのデイシリアライズができる() { - @Language("JSON") val json = """{ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "misskey": "https://misskey-hub.net/ns#", - "_misskey_content": "misskey:_misskey_content", - "_misskey_quote": "misskey:_misskey_quote", - "_misskey_reaction": "misskey:_misskey_reaction", - "_misskey_votes": "misskey:_misskey_votes", - "isCat": "misskey:isCat", - "vcard": "http://www.w3.org/2006/vcard/ns#" - } - ], - "id": "https://misskey.usbharu.dev/notes/9f2i9cm88e/activity", - "actor": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "type": "Create", - "published": "2023-05-22T14:26:53.600Z", - "object": { - "id": "https://misskey.usbharu.dev/notes/9f2i9cm88e", - "type": "Note", - "attributedTo": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "content": "

@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…

", - "_misskey_content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…", - "source": { - "content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…", - "mediaType": "text/x.misskeymarkdown" - }, - "published": "2023-05-22T14:26:53.600Z", - "to": [ - "https://misskey.usbharu.dev/users/97ws8y3rj6/followers" - ], - "cc": [ - "https://www.w3.org/ns/activitystreams#Public", - "https://calckey.jp/users/9bu1xzwjyb" - ], - "inReplyTo": "https://calckey.jp/notes/9f2i7ymf1d", - "attachment": [], - "sensitive": false, - "tag": [ - { - "type": "Mention", - "href": "https://calckey.jp/users/9bu1xzwjyb", - "name": "@trapezial@calckey.jp" - } - ] - }, - "to": [ - "https://misskey.usbharu.dev/users/97ws8y3rj6/followers" - ], - "cc": [ - "https://www.w3.org/ns/activitystreams#Public", - "https://calckey.jp/users/9bu1xzwjyb" - ] -} -""" - - val objectMapper = ActivityPubConfig().objectMapper() - - objectMapper.readValue(json) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DeleteSerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DeleteSerializeTest.kt deleted file mode 100644 index 6f964232..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DeleteSerializeTest.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class DeleteSerializeTest { - @Test - fun Misskeyの発行するJSONをデシリアライズできる() { - @Language("JSON") val json = """{ - "@context" : [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { - "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers", - "sensitive" : "as:sensitive", - "Hashtag" : "as:Hashtag", - "quoteUrl" : "as:quoteUrl", - "toot" : "http://joinmastodon.org/ns#", - "Emoji" : "toot:Emoji", - "featured" : "toot:featured", - "discoverable" : "toot:discoverable", - "schema" : "http://schema.org#", - "PropertyValue" : "schema:PropertyValue", - "value" : "schema:value" - } ], - "type" : "Delete", - "actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6", - "object" : { - "id" : "https://misskey.usbharu.dev/notes/9lkwqnwqk9", - "type" : "Tombstone" - }, - "published" : "2023-11-02T15:30:34.160Z", - "id" : "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69" -} -""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - val expected = Delete( - actor = "https://misskey.usbharu.dev/users/97ws8y3rj6", - id = "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69", - `object` = Tombstone( - id = "https://misskey.usbharu.dev/notes/9lkwqnwqk9", - ), - published = "2023-11-02T15:30:34.160Z", - ) - expected.context = Constant.context - assertEquals(expected, readValue) - } - - @Test - fun シリアライズできる() { - val delete = Delete( - actor = "https://misskey.usbharu.dev/users/97ws8y3rj6", - id = "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69", - `object` = Tombstone( - id = "https://misskey.usbharu.dev/notes/9lkwqnwqk9", - ), - published = "2023-11-02T15:30:34.160Z", - ) - - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(delete) - val expected = - """{"type":"Delete","actor":"https://misskey.usbharu.dev/users/97ws8y3rj6","id":"https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69","object":{"type":"Tombstone","id":"https://misskey.usbharu.dev/notes/9lkwqnwqk9"},"published":"2023-11-02T15:30:34.160Z"}""" - assertEquals(expected, actual) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DocumentTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DocumentTest.kt deleted file mode 100644 index 168e88d4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/DocumentTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Test - -class DocumentTest { - @Test - fun Documentをデシリアライズできる() { - @Language("JSON") val json = """{ - "type": "Document", - "mediaType": "image/webp", - "url": "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/81ec9ad1-2581-466e-b90c-d9d2350ab95c.webp", - "name": "ALTテスト" - }""" - - val objectMapper = ActivityPubConfig().objectMapper() - - objectMapper.readValue(json) - } - - @Test - fun nameがnullなDocumentのデイシリアライズができる() { - //language=JSON - val json = """{ - "type": "Document", - "mediaType": "image/webp", - "url": "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/81ec9ad1-2581-466e-b90c-d9d2350ab95c.webp", - "name": null - }""" - - val objectMapper = ActivityPubConfig().objectMapper() - - objectMapper.readValue(json) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt deleted file mode 100644 index 9c9530cc..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLdSerializeTest.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class JsonLdSerializeTest { - @Test - fun contextが文字列のときデシリアライズできる() { - //language=JSON - val json = """{"@context":"https://example.com"}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals(JsonLd(listOf(StringOrObject("https://example.com"))), readValue) - } - - @Test - fun contextが文字列の配列のときデシリアライズできる() { - //language=JSON - val json = """{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals( - JsonLd( - listOf( - StringOrObject("https://example.com"), - StringOrObject("https://www.w3.org/ns/activitystreams") - ) - ), readValue - ) - } - - @Test - fun contextがnullのとき空のlistとして解釈してデシリアライズする() { - //language=JSON - val json = """{"@context":null}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals(JsonLd(emptyList()), readValue) - } - - @Test - fun contextがnullを含む文字列の配列のときnullを無視してデシリアライズできる() { - //language=JSON - val json = """{"@context":["https://example.com",null,"https://www.w3.org/ns/activitystreams"]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals( - JsonLd( - listOf( - StringOrObject("https://example.com"), - StringOrObject("https://www.w3.org/ns/activitystreams") - ) - ), readValue - ) - } - - @Test - fun contextがオブジェクトのとき無視してデシリアライズする() { - //language=JSON - val json = """{"@context":{"hoge": "fuga"}}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals(JsonLd(listOf(StringOrObject(mapOf("hoge" to "fuga")))), readValue) - } - - @Test - fun contextがオブジェクトを含む文字列の配列のときオブジェクトを無視してデシリアライズする() { - //language=JSON - val json = """{"@context":["https://example.com",{"hoge": "fuga"},"https://www.w3.org/ns/activitystreams"]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals( - JsonLd( - listOf( - StringOrObject("https://example.com"), - StringOrObject(mapOf("hoge" to "fuga")), - StringOrObject("https://www.w3.org/ns/activitystreams") - ) - ), readValue - ) - } - - @Test - fun contextが配列の配列のとき無視してデシリアライズする() { - //language=JSON - val json = """{"@context":[["a","b"],["c","d"]]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals(JsonLd(emptyList()), readValue) - } - - @Test - fun contextが空のとき無視してシリアライズする() { - val jsonLd = JsonLd(emptyList()) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("{}", actual) - } - - @Test - fun contextがnullのとき無視してシリアライズする() { - val jsonLd = JsonLd(listOf(null)) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("{}", actual) - } - - @Test - fun contextが文字列のとき文字列としてシリアライズされる() { - val jsonLd = JsonLd(listOf(StringOrObject("https://example.com"))) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("""{"@context":"https://example.com"}""", actual) - } - - @Test - fun contextが文字列の配列のとき配列としてシリアライズされる() { - val jsonLd = JsonLd( - listOf( - StringOrObject("https://example.com"), - StringOrObject("https://www.w3.org/ns/activitystreams") - ) - ) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("""{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""", actual) - } - - @Test - fun contextがオブジェクトのときシリアライズできる() { - val jsonLd = JsonLd( - listOf( - StringOrObject(mapOf("hoge" to "fuga")) - ) - ) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("""{"@context":{"hoge":"fuga"}}""", actual) - - } - - @Test - fun contextが複数のオブジェクトのときシリアライズできる() { - val jsonLd = JsonLd( - listOf( - StringOrObject(mapOf("hoge" to "fuga")), - StringOrObject(mapOf("foo" to "bar")) - ) - ) - - val objectMapper = ActivityPubConfig().objectMapper() - - val actual = objectMapper.writeValueAsString(jsonLd) - - assertEquals("""{"@context":[{"hoge":"fuga"},{"foo":"bar"}]}""", actual) - } - - @Test - fun contextが複数のオブジェクトのときデシリアライズできる() { - //language=JSON - val json = """{"@context":["https://example.com",{"hoge": "fuga"},{"foo": "bar"}]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals( - JsonLd( - listOf( - StringOrObject("https://example.com"), - StringOrObject(mapOf("hoge" to "fuga")), - StringOrObject(mapOf("foo" to "bar")) - ) - ), readValue - ) - } - - @Test - fun contextがオブジェクトのときデシリアライズできる() { - //language=JSON - val json = """{"@context":{"hoge": "fuga"}}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - assertEquals( - JsonLd( - listOf( - StringOrObject(mapOf("hoge" to "fuga")) - ) - ), readValue - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/KeySerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/KeySerializeTest.kt deleted file mode 100644 index c0ffbfaf..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/KeySerializeTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.junit.jupiter.api.Test - -class KeySerializeTest { - @Test - fun Keyのデシリアライズができる() { - //language=JSON - val trimIndent = """ - { - "id": "https://mastodon.social/users/Gargron#main-key", - "owner": "https://mastodon.social/users/Gargron", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXc4vkECU2/CeuSo1wtn\nFoim94Ne1jBMYxTZ9wm2YTdJq1oiZKif06I2fOqDzY/4q/S9uccrE9Bkajv1dnkO\nVm31QjWlhVpSKynVxEWjVBO5Ienue8gND0xvHIuXf87o61poqjEoepvsQFElA5ym\novljWGSA/jpj7ozygUZhCXtaS2W5AD5tnBQUpcO0lhItYPYTjnmzcc4y2NbJV8hz\n2s2G8qKv8fyimE23gY1XrPJg+cRF+g4PqFXujjlJ7MihD9oqtLGxbu7o1cifTn3x\nBfIdPythWu5b4cujNsB3m3awJjVmx+MHQ9SugkSIYXV0Ina77cTNS0M2PYiH1PFR\nTwIDAQAB\n-----END PUBLIC KEY-----\n" - } - """.trimIndent() - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(trimIndent) - - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt deleted file mode 100644 index 8c4b3f9d..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/NoteSerializeTest.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class NoteSerializeTest { - @Test - fun Noteのシリアライズができる() { - val note = Note( - id = "https://example.com", - attributedTo = "https://example.com/actor", - content = "Hello", - published = "2023-05-20T10:28:17.308Z", - ) - - val objectMapper = ActivityPubConfig().objectMapper() - - val writeValueAsString = objectMapper.writeValueAsString(note) - - assertEquals( - """{"type":"Note","id":"https://example.com","attributedTo":"https://example.com/actor","content":"Hello","published":"2023-05-20T10:28:17.308Z","sensitive":false}""", - writeValueAsString - ) - } - - @Test - fun Noteのデシリアライズができる() { - //language=JSON - val json = """{ - "id": "https://misskey.usbharu.dev/notes/9f2i9cm88e", - "type": "Note", - "attributedTo": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "content": "

@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…

", - "_misskey_content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…", - "source": { - "content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…", - "mediaType": "text/x.misskeymarkdown" - }, - "published": "2023-05-22T14:26:53.600Z", - "to": [ - "https://misskey.usbharu.dev/users/97ws8y3rj6/followers" - ], - "cc": [ - "https://www.w3.org/ns/activitystreams#Public", - "https://calckey.jp/users/9bu1xzwjyb" - ], - "inReplyTo": "https://calckey.jp/notes/9f2i7ymf1d", - "attachment": [], - "sensitive": false, - "tag": [ - - ] - }""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - val note = Note( - id = "https://misskey.usbharu.dev/notes/9f2i9cm88e", - type = listOf("Note"), - attributedTo = "https://misskey.usbharu.dev/users/97ws8y3rj6", - content = "

@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…

", - published = "2023-05-22T14:26:53.600Z", - to = listOf("https://misskey.usbharu.dev/users/97ws8y3rj6/followers"), - cc = listOf(public, "https://calckey.jp/users/9bu1xzwjyb"), - sensitive = false, - inReplyTo = "https://calckey.jp/notes/9f2i7ymf1d", - attachment = emptyList() - ) - assertEquals(note, readValue) - } - - @Test - fun 絵文字付きNoteのデシリアライズができる() { - val json = """{ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value" - } - ], - "id": "https://misskey.usbharu.dev/notes/9nj1omt1rn", - "type": "Note", - "attributedTo": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "content": "

​:oyasumi:​

", - "_misskey_content": ":oyasumi:", - "source": { - "content": ":oyasumi:", - "mediaType": "text/x.misskeymarkdown" - }, - "published": "2023-12-21T17:32:36.853Z", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://misskey.usbharu.dev/users/97ws8y3rj6/followers" - ], - "inReplyTo": null, - "attachment": [], - "sensitive": false, - "tag": [ - { - "id": "https://misskey.usbharu.dev/emojis/oyasumi", - "type": "Emoji", - "name": ":oyasumi:", - "updated": "2023-04-07T08:21:25.246Z", - "icon": { - "type": "Image", - "mediaType": "image/png", - "url": "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/cf8db710-1d70-4076-8a00-dbb28131096e.png" - } - } - ] -}""" - - - val objectMapper = ActivityPubConfig().objectMapper() - - val expected = Note( - type = emptyList(), - id = "https://misskey.usbharu.dev/notes/9nj1omt1rn", - attributedTo = "https://misskey.usbharu.dev/users/97ws8y3rj6", - content = "

\u200B:oyasumi:\u200B

", - published = "2023-12-21T17:32:36.853Z", - to = listOf("https://www.w3.org/ns/activitystreams#Public"), - cc = listOf("https://misskey.usbharu.dev/users/97ws8y3rj6/followers"), - sensitive = false, - inReplyTo = null, - attachment = emptyList(), - tag = listOf( - Emoji( - type = emptyList(), - name = ":oyasumi:", - id = "https://misskey.usbharu.dev/emojis/oyasumi", - updated = "2023-04-07T08:21:25.246Z", - icon = Image( - type = emptyList(), - mediaType = "image/png", - url = "https://s3misskey.usbharu.dev/misskey-minio/misskey-minio/data/cf8db710-1d70-4076-8a00-dbb28131096e.png" - ) - ) - ) - ) - - expected.context = Constant.context - - val note = objectMapper.readValue(json) - - assertThat(note).isEqualTo(expected) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/PersonSerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/PersonSerializeTest.kt deleted file mode 100644 index ddd39162..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/PersonSerializeTest.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.junit.jupiter.api.Test - -class PersonSerializeTest { - @Test - fun MastodonのPersonのデシリアライズができる() { - val personString = """ - { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], - "id": "https://mastodon.social/users/Gargron", - "type": "Person", - "following": "https://mastodon.social/users/Gargron/following", - "followers": "https://mastodon.social/users/Gargron/followers", - "inbox": "https://mastodon.social/users/Gargron/inbox", - "outbox": "https://mastodon.social/users/Gargron/outbox", - "featured": "https://mastodon.social/users/Gargron/collections/featured", - "featuredTags": "https://mastodon.social/users/Gargron/collections/tags", - "preferredUsername": "Gargron", - "name": "Eugen Rochko", - "summary": "\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e", - "url": "https://mastodon.social/@Gargron", - "manuallyApprovesFollowers": false, - "discoverable": true, - "published": "2016-03-16T00:00:00Z", - "devices": "https://mastodon.social/users/Gargron/collections/devices", - "alsoKnownAs": [ - "https://tooting.ai/users/Gargron" - ], - "publicKey": { - "id": "https://mastodon.social/users/Gargron#main-key", - "owner": "https://mastodon.social/users/Gargron", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXc4vkECU2/CeuSo1wtn\nFoim94Ne1jBMYxTZ9wm2YTdJq1oiZKif06I2fOqDzY/4q/S9uccrE9Bkajv1dnkO\nVm31QjWlhVpSKynVxEWjVBO5Ienue8gND0xvHIuXf87o61poqjEoepvsQFElA5ym\novljWGSA/jpj7ozygUZhCXtaS2W5AD5tnBQUpcO0lhItYPYTjnmzcc4y2NbJV8hz\n2s2G8qKv8fyimE23gY1XrPJg+cRF+g4PqFXujjlJ7MihD9oqtLGxbu7o1cifTn3x\nBfIdPythWu5b4cujNsB3m3awJjVmx+MHQ9SugkSIYXV0Ina77cTNS0M2PYiH1PFR\nTwIDAQAB\n-----END PUBLIC KEY-----\n" - }, - "tag": [], - "attachment": [ - { - "type": "PropertyValue", - "name": "Patreon", - "value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - }, - { - "type": "PropertyValue", - "name": "GitHub", - "value": "\u003ca href=\"https://github.com/Gargron\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/Gargron\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e" - } - ], - "endpoints": { - "sharedInbox": "https://mastodon.social/inbox" - }, - "icon": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg" - }, - "image": { - "type": "Image", - "mediaType": "image/jpeg", - "url": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg" - } - } - - """.trimIndent() - - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(personString) - } - - @Test - fun MisskeyのnameがnullのPersonのデシリアライズができる() { - //language=JSON - val json = """{ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "misskey": "https://misskey-hub.net/ns#", - "_misskey_content": "misskey:_misskey_content", - "_misskey_quote": "misskey:_misskey_quote", - "_misskey_reaction": "misskey:_misskey_reaction", - "_misskey_votes": "misskey:_misskey_votes", - "_misskey_summary": "misskey:_misskey_summary", - "isCat": "misskey:isCat", - "vcard": "http://www.w3.org/2006/vcard/ns#" - } - ], - "type": "Person", - "id": "https://misskey.usbharu.dev/users/9ghwhv9zgg", - "inbox": "https://misskey.usbharu.dev/users/9ghwhv9zgg/inbox", - "outbox": "https://misskey.usbharu.dev/users/9ghwhv9zgg/outbox", - "followers": "https://misskey.usbharu.dev/users/9ghwhv9zgg/followers", - "following": "https://misskey.usbharu.dev/users/9ghwhv9zgg/following", - "featured": "https://misskey.usbharu.dev/users/9ghwhv9zgg/collections/featured", - "sharedInbox": "https://misskey.usbharu.dev/inbox", - "endpoints": { - "sharedInbox": "https://misskey.usbharu.dev/inbox" - }, - "url": "https://misskey.usbharu.dev/@relay_test", - "preferredUsername": "relay_test", - "name": null, - "summary": null, - "_misskey_summary": null, - "icon": null, - "image": null, - "tag": [], - "manuallyApprovesFollowers": true, - "discoverable": true, - "publicKey": { - "id": "https://misskey.usbharu.dev/users/9ghwhv9zgg#main-key", - "type": "Key", - "owner": "https://misskey.usbharu.dev/users/9ghwhv9zgg", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2n5yekTaI4ex5VDWzQfE\nJpWMURAMWl8RcXHLPyLQVQ/PrHp7qatGXmKJUnAOBcq1cwk+VCqTEqx8vJCOZsr1\nMq+D3FMcFdwgtJ0nivPJPx2457b5kfQ4LTkWajcFhj2qixa/XFq6hHei3LDaE6hJ\nGQbdj9NTVlMd7VpiFQkoU09vAPUwGxRoP9Qbc/sh7jrKYFB3iRmY/+zOc+PFpnfn\nG8V1d2v+lnkb9f7t0Z8y2ckk6TVcLPRZktF15eGClVptlgts3hwhrcyrpBs2Dn0U\n35KgIhkhZGAjzk0uyplpfKcserXuGvsjJvelZ3BtMGsuR4kGLHrmiRQp23mIoA1I\n8tfVuV0zPOyO3ruLk2fOjoeZ4XvFHGRNKo66Qx055/8G8Ug5vU8lvIGXm9sflaA9\ntR3AKDNsyxEfjAfrfgJ7cwlKSlLZmkU51jtYEqJ48ZkiIa6fMC0m4QGXdaXmhFWC\no1sGoIErRFpRHewdGlLC9S8R/cMxjex+n8maF0yh79y7aVvU+TS6pRWg5wYjY8r3\nZqAVg/PGRVGAbjVdIdcsjH5ClwAFBW16S633D3m7HJypwwVCzVOvMZqPqcQ/2o8c\nUk+xa88xQG+OPqoAaQqyV9iqsmCMgYM/AcX/BC2h7L2mE/PWoXnoCxGPxr5uvyBf\nHQakDGg4pFZcpVNrDlYo260CAwEAAQ==\n-----END PUBLIC KEY-----\n" - }, - "isCat": false -}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - objectMapper.readValue(json) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/RejectTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/RejectTest.kt deleted file mode 100644 index 65e26aac..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/RejectTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.activitypub.domain.model - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.assertj.core.api.Assertions.assertThat -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Test -import org.springframework.boot.test.json.BasicJsonTester - -class RejectTest { - @Test - fun rejectDeserializeTest() { - @Language("JSON") val json = """{ - "@context" : [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { - "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers", - "sensitive" : "as:sensitive", - "Hashtag" : "as:Hashtag", - "quoteUrl" : "as:quoteUrl", - "toot" : "http://joinmastodon.org/ns#", - "Emoji" : "toot:Emoji", - "featured" : "toot:featured", - "discoverable" : "toot:discoverable", - "schema" : "http://schema.org#", - "PropertyValue" : "schema:PropertyValue", - "value" : "schema:value" - } ], - "type" : "Reject", - "actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6", - "object" : { - "id" : "https://misskey.usbharu.dev/follows/9mxh6mawru/97ws8y3rj6", - "type" : "Follow", - "actor" : "https://test-hideout.usbharu.dev/users/test-user2", - "object" : "https://misskey.usbharu.dev/users/97ws8y3rj6" - }, - "id" : "https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0" -} -""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val reject = objectMapper.readValue(json) - - val expected = Reject( - "https://misskey.usbharu.dev/users/97ws8y3rj6", - "https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0", - Follow( - apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6", - actor = "https://test-hideout.usbharu.dev/users/test-user2", - id = "https://misskey.usbharu.dev/follows/9mxh6mawru/97ws8y3rj6" - ) - ).apply { - context = Constant.context - } - - assertThat(reject).isEqualTo(expected) - } - - @Test - fun rejectSerializeTest() { - val basicJsonTester = BasicJsonTester(javaClass) - - val reject = Reject( - "https://misskey.usbharu.dev/users/97ws8y3rj6", - "https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0", - Follow( - apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6", - actor = "https://test-hideout.usbharu.dev/users/test-user2" - ) - ).apply { - context = listOf( - StringOrObject("https://www.w3.org/ns/activitystreams"), - StringOrObject("https://w3id.org/security/v1") - ) - } - - val objectMapper = ActivityPubConfig().objectMapper() - - val writeValueAsString = objectMapper.writeValueAsString(reject) - - val from = basicJsonTester.from(writeValueAsString) - - assertThat(from).extractingJsonPathStringValue("$.actor") - .isEqualTo("https://misskey.usbharu.dev/users/97ws8y3rj6") - assertThat(from).extractingJsonPathStringValue("$.id") - .isEqualTo("https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0") - assertThat(from).extractingJsonPathStringValue("$.type").isEqualTo("Reject") - assertThat(from).extractingJsonPathStringValue("$.object.actor") - .isEqualTo("https://test-hideout.usbharu.dev/users/test-user2") - assertThat(from).extractingJsonPathStringValue("$.object.object") - .isEqualTo("https://misskey.usbharu.dev/users/97ws8y3rj6") - assertThat(from).extractingJsonPathStringValue("$.object.type").isEqualTo("Follow") - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/UndoTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/UndoTest.kt deleted file mode 100644 index f4688916..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/UndoTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.activitypub.domain.model - -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Test -import java.time.Clock -import java.time.Instant -import java.time.ZoneId - -class UndoTest { - @Test - fun Undoのシリアライズができる() { - val undo = Undo( - emptyList(), - "https://follower.example.com/", - "https://follower.example.com/undo/1", - Follow( - emptyList(), - "https://follower.example.com/users/", - actor = "https://follower.exaple.com/users/1" - ), - Instant.now(Clock.tickMillis(ZoneId.systemDefault())).toString() - ) - val writeValueAsString = ActivityPubConfig().objectMapper().writeValueAsString(undo) - println(writeValueAsString) - } - - @Test - fun Undoをデシリアライズ出来る() { - @Language("JSON") - val json = """ - { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "misskey": "https://misskey-hub.net/ns#", - "_misskey_content": "misskey:_misskey_content", - "_misskey_quote": "misskey:_misskey_quote", - "_misskey_reaction": "misskey:_misskey_reaction", - "_misskey_votes": "misskey:_misskey_votes", - "isCat": "misskey:isCat", - "vcard": "http://www.w3.org/2006/vcard/ns#" - } - ], - "type": "Undo", - "id": "https://misskey.usbharu.dev/follows/97ws8y3rj6/9ezbh8qrh0/undo", - "actor": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "object": { - "id": "https://misskey.usbharu.dev/follows/97ws8y3rj6/9ezbh8qrh0", - "type": "Follow", - "actor": "https://misskey.usbharu.dev/users/97ws8y3rj6", - "object": "https://test-hideout.usbharu.dev/users/test" - }, - "published": "2023-05-20T10:28:17.308Z" -} - - """.trimIndent() - - val undo = ActivityPubConfig().objectMapper().readValue(json, Undo::class.java) - println(undo) - } - - @Test - fun MastodonのUndoのデシリアライズができる() { - //language=JSON - val json = """{ - "@context" : "https://www.w3.org/ns/activitystreams", - "id" : "https://kb.usbharu.dev/users/usbharu#follows/12/undo", - "type" : "Undo", - "actor" : "https://kb.usbharu.dev/users/usbharu", - "object" : { - "id" : "https://kb.usbharu.dev/0347b269-4dcb-4eb1-b8c4-b5f157bb6957", - "type" : "Follow", - "actor" : "https://kb.usbharu.dev/users/usbharu", - "object" : "https://test-hideout.usbharu.dev/users/testuser15" - } -}""".trimIndent() - - val undo = ActivityPubConfig().objectMapper().readValue(json, Undo::class.java) - - println(undo) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectSerializeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectSerializeTest.kt deleted file mode 100644 index a4981fe9..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectSerializeTest.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.activitypub.domain.model.objects - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.ActivityPubConfig -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class ObjectSerializeTest { - @Test - fun typeが文字列のときデシリアライズできる() { - //language=JSON - val json = """{"type": "Object"}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - val expected = Object( - listOf("Object") - ) - assertEquals(expected, readValue) - } - - @Test - fun typeが文字列の配列のときデシリアライズできる() { - //language=JSON - val json = """{"type": ["Hoge","Object"]}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - val expected = Object( - listOf("Hoge", "Object") - ) - - assertEquals(expected, readValue) - } - - @Test - fun typeが空のとき無視してデシリアライズする() { - //language=JSON - val json = """{"type": ""}""" - - val objectMapper = ActivityPubConfig().objectMapper() - - val readValue = objectMapper.readValue(json) - - val expected = Object( - emptyList() - ) - - assertEquals(expected, readValue) - } - -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt deleted file mode 100644 index 76239bdd..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.activitypub.interfaces.api.actor - -import dev.usbharu.hideout.activitypub.domain.model.Image -import dev.usbharu.hideout.activitypub.domain.model.Key -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doThrow -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders - -@ExtendWith(MockitoExtension::class) -class ActorAPControllerImplTest { - - private lateinit var mockMvc: MockMvc - - @Mock - private lateinit var apUserService: APUserService - - @InjectMocks - private lateinit var userAPControllerImpl: UserAPControllerImpl - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders - .standaloneSetup(userAPControllerImpl) - .setMessageConverters(MappingJackson2HttpMessageConverter(ActivityPubConfig().objectMapper())) - .build() - } - - @Test - fun `userAp 存在するユーザーにGETするとPersonが返ってくる`(): Unit = runTest { - val person = Person( - name = "Hoge", - id = "https://example.com/users/hoge", - preferredUsername = "hoge", - summary = "fuga", - inbox = "https://example.com/users/hoge/inbox", - outbox = "https://example.com/users/hoge/outbox", - url = "https://example.com/users/hoge", - icon = Image( - mediaType = "image/jpeg", - url = "https://example.com/users/hoge/icon.jpg" - ), - publicKey = Key( - id = "https://example.com/users/hoge#pubkey", - owner = "https://example.com/users/hoge", - publicKeyPem = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----" - ), - endpoints = mapOf("sharedInbox" to "https://example.com/inbox"), - followers = "https://example.com/users/hoge/followers", - following = "https://example.com/users/hoge/following", - manuallyApprovesFollowers = false - ) - whenever(apUserService.getPersonByName(eq("hoge"))).doReturn(person) - - val objectMapper = ActivityPubConfig().objectMapper() - - mockMvc - .get("/users/hoge") - .asyncDispatch() - .andDo { print() } - .andExpect { status { isOk() } } - .andExpect { content { this.json(objectMapper.writeValueAsString(person)) } } - } - - @Test - fun `userAP 存在しないユーザーにGETすると404が返ってくる`() = runTest { - whenever(apUserService.getPersonByName(eq("fuga"))).doThrow(UserNotFoundException::class) - - mockMvc - .get("/users/fuga") - .asyncDispatch() - .andExpect { status { isNotFound() } } - } - - @Test - fun `userAP POSTすると405が返ってくる`() { - mockMvc - .post("/users/hoge") - .andExpect { status { isMethodNotAllowed() } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt deleted file mode 100644 index 5ff88e2f..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt +++ /dev/null @@ -1,570 +0,0 @@ -/* - * 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.activitypub.interfaces.api.inbox - -import dev.usbharu.hideout.activitypub.domain.exception.JsonParseException -import dev.usbharu.hideout.activitypub.service.common.APService -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker -import dev.usbharu.hideout.util.Base64Util -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import java.net.URI -import java.security.MessageDigest -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.* - -@ExtendWith(MockitoExtension::class) -class InboxControllerImplTest { - - private lateinit var mockMvc: MockMvc - - @Spy - private val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("https://example.com").toURL())) - - @Mock - private lateinit var apService: APService - - @InjectMocks - private lateinit var inboxController: InboxControllerImpl - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(inboxController).build() - } - - - private val dateTimeFormatter: DateTimeFormatter = - DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - - @Test - fun `inbox 正常なPOSTリクエストをしたときAcceptが返ってくる`() = runTest { - - - val json = """{"type":"Follow"}""" - whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow) - whenever( - apService.processActivity( - eq(json), eq(ActivityType.Follow), any(), any() - - ) - ).doReturn(Unit) - - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - - mockMvc.post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=" + digest) - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `inbox parseActivityに失敗したときAcceptが返ってくる`() = runTest { - val json = """{"type":"Hoge"}""" - whenever(apService.parseActivity(eq(json))).doThrow(JsonParseException::class) - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc.post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `inbox processActivityに失敗したときAcceptが返ってくる`() = runTest { - val json = """{"type":"Follow"}""" - whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow) - whenever( - apService.processActivity( - eq(json), eq(ActivityType.Follow), any(), any() - ) - ).doThrow(FailedToGetResourcesException::class) - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc.post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `inbox GETリクエストには405を返す`() { - mockMvc.get("/inbox").andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `user-inbox 正常なPOSTリクエストをしたときAcceptが返ってくる`() = runTest { - - - val json = """{"type":"Follow"}""" - whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow) - whenever(apService.processActivity(eq(json), eq(ActivityType.Follow), any(), any())).doReturn( - Unit - ) - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc.post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `user-inbox parseActivityに失敗したときAcceptが返ってくる`() = runTest { - val json = """{"type":"Hoge"}""" - whenever(apService.parseActivity(eq(json))).doThrow(JsonParseException::class) - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc.post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `user-inbox processActivityに失敗したときAcceptが返ってくる`() = runTest { - val json = """{"type":"Follow"}""" - whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow) - whenever( - apService.processActivity( - eq(json), eq(ActivityType.Follow), any(), any() - ) - ).doThrow(FailedToGetResourcesException::class) - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc.post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "a") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - }.asyncDispatch().andExpect { - status { isAccepted() } - } - - } - - @Test - fun `user-inbox GETリクエストには405を返す`() { - mockMvc.get("/users/hoge/inbox").andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `inbox Dateヘッダーが無いと400`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - } - .asyncDispatch() - .andExpect { - status { - isBadRequest() - } - } - } - - @Test - fun `user-inbox Dateヘッダーが無いと400`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - } - .asyncDispatch() - .andExpect { - status { - isBadRequest() - } - } - } - - @Test - fun `inbox Dateヘッダーが未来だと401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().plusDays(1).format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `user-inbox Dateヘッダーが未来だと401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().plusDays(1).format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `inbox Dateヘッダーが過去過ぎると401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().minusDays(1).format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `user-inbox Dateヘッダーが過去過ぎると401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().minusDays(1).format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `inbox Hostヘッダーが無いと400`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isBadRequest() - } - } - } - - @Test - fun `user-inbox Hostヘッダーが無いと400`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { - isBadRequest() - } - } - } - - @Test - fun `inbox Hostヘッダーが間違ってると401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Host", "example.jp") - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `user-inbox Hostヘッダーが間違ってると401`() { - val json = """{"type":"Follow"}""" - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Host", "example.jp") - } - .asyncDispatch() - .andExpect { - status { - isUnauthorized() - } - } - } - - @Test - fun `inbox Digestヘッダーがないと400`() = runTest { - - - val json = """{"type":"Follow"}""" - - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { isBadRequest() } - } - - } - - @Test - fun `inbox Digestヘッダーが間違ってると401`() = runTest { - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(("$json aaaaaaaa").toByteArray())) - - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - } - - @Test - fun `user-inbox Digestヘッダーがないと400`() = runTest { - - - val json = """{"type":"Follow"}""" - - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - } - .asyncDispatch() - .andExpect { - status { isBadRequest() } - } - - } - - @Test - fun `user-inbox Digestヘッダーが間違ってると401`() = runTest { - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(("$json aaaaaaaa").toByteArray())) - - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - } - - @Test - fun `inbox Signatureヘッダーがないと401`() = runTest { - - - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - - } - - @Test - fun `inbox Signatureヘッダーが空だと401`() = runTest { - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - - mockMvc - .post("/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - } - - @Test - fun `user-inbox Digestヘッダーがないと401`() = runTest { - - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - - } - - @Test - fun `user-inbox Digestヘッダーが空だと401`() = runTest { - val json = """{"type":"Follow"}""" - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(json.toByteArray())) - - mockMvc - .post("/users/hoge/inbox") { - content = json - contentType = MediaType.APPLICATION_JSON - header("Signature", "") - header("Host", "example.com") - header("Date", ZonedDateTime.now().format(dateTimeFormatter)) - header("Digest", "SHA-256=$digest") - } - .asyncDispatch() - .andExpect { - status { isUnauthorized() } - } - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImplTest.kt deleted file mode 100644 index cb60896e..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImplTest.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.activitypub.interfaces.api.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.service.objects.note.NoteApApiService -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import java.net.URL - -@ExtendWith(MockitoExtension::class) -class NoteApControllerImplTest { - - private lateinit var mockMvc: MockMvc - - @Mock - private lateinit var noteApApiService: NoteApApiService - - @InjectMocks - private lateinit var noteApControllerImpl: NoteApControllerImpl - - @BeforeEach - fun setUp() { - - mockMvc = MockMvcBuilders.standaloneSetup(noteApControllerImpl) -// .apply( -// springSecurity( -// FilterChainProxy( -// DefaultSecurityFilterChain( -// AnyRequestMatcher.INSTANCE -// ) -// ) -// ) -// ) - .build() - } - - @Test - fun `postAP 匿名で取得できる`() = runTest { - SecurityContextHolder.clearContext() - val note = Note( - id = "https://example.com/users/hoge/posts/1234", - attributedTo = "https://example.com/users/hoge", - content = "Hello", - published = "2023-11-02T15:30:34.160Z" - ) - whenever(noteApApiService.getNote(eq(1234), isNull())).doReturn( - note - ) - - val objectMapper = ActivityPubConfig().objectMapper() - - mockMvc - .get("/users/hoge/posts/1234") { -// with(anonymous()) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(note)) } } - } - - @Test - fun `postAP 存在しない場合は404`() = runTest { - SecurityContextHolder.clearContext() - whenever(noteApApiService.getNote(eq(123), isNull())).doReturn(null) - - mockMvc - .get("/users/hoge/posts/123") { -// with(anonymous()) - } - .asyncDispatch() - .andExpect { status { isNotFound() } } - } - - @Test - fun `postAP 認証に成功している場合userIdがnullでない`() = runTest { - val note = Note( - id = "https://example.com/users/hoge/posts/1234", - attributedTo = "https://example.com/users/hoge", - content = "Hello", - published = "2023-11-02T15:30:34.160Z" - ) - whenever(noteApApiService.getNote(eq(1234), isNotNull())).doReturn(note) - - val objectMapper = ActivityPubConfig().objectMapper() - - val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken( - "", HttpRequest( - URL("https://follower.example.com"), - HttpHeaders( - mapOf() - ), HttpMethod.GET - ) - ).apply { details = HttpSignatureUser("fuga", "follower.example.com", 123, true, true, mutableListOf()) } - SecurityContextHolder.getContext().authentication = preAuthenticatedAuthenticationToken - - mockMvc.get("/users/hoge/posts/1234") { -// with( -// authentication( -// preAuthenticatedAuthenticationToken -// ) -// ) - }.asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(note)) } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImplTest.kt deleted file mode 100644 index 927bd784..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImplTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.activitypub.interfaces.api.outbox - -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.junit.jupiter.MockitoExtension -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders - -@ExtendWith(MockitoExtension::class) -class OutboxControllerImplTest { - - private lateinit var mockMvc: MockMvc - - @InjectMocks - private lateinit var outboxController: OutboxControllerImpl - - @BeforeEach - fun setUp() { - mockMvc = - MockMvcBuilders.standaloneSetup(outboxController).build() - } - - @Test - fun `outbox GETに501を返す`() { - mockMvc - .get("/outbox") - .asyncDispatch() - .andDo { print() } - .andExpect { status { isNotImplemented() } } - } - - @Test - fun `user-outbox GETに501を返す`() { - mockMvc - .get("/users/hoge/outbox") - .asyncDispatch() - .andDo { print() } - .andExpect { status { isNotImplemented() } } - } - - @Test - fun `outbox POSTに501を返す`() { - mockMvc - .post("/outbox") - .asyncDispatch() - .andDo { print() } - .andExpect { status { isNotImplemented() } } - } - - @Test - fun `user-outbox POSTに501を返す`() { - mockMvc - .post("/users/hoge/outbox") - .asyncDispatch() - .andDo { print() } - .andExpect { status { isNotImplemented() } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerControllerTest.kt deleted file mode 100644 index a55770d4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerControllerTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.activitypub.interfaces.api.webfinger - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger -import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doThrow -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import utils.UserBuilder - -@ExtendWith(MockitoExtension::class) -class WebFingerControllerTest { - - private lateinit var mockMvc: MockMvc - - @Mock - private lateinit var webFingerApiService: WebFingerApiService - - @Mock - private lateinit var applicationConfig: ApplicationConfig - - @InjectMocks - private lateinit var webFingerController: WebFingerController - - @BeforeEach - fun setUp() { - this.mockMvc = MockMvcBuilders.standaloneSetup(webFingerController).build() - } - - @Test - fun `webfinger 存在するacctを指定したとき200 OKでWebFingerのレスポンスが返ってくる`() = runTest { - - val user = UserBuilder.localUserOf() - whenever( - webFingerApiService.findByNameAndDomain( - eq("hoge"), - eq("example.com") - ) - ).doReturn(user) - - val contentAsString = mockMvc.perform(get("/.well-known/webfinger?resource=acct:hoge@example.com")) - .andDo(print()) - .andExpect(status().isOk()) - .andReturn() - .response - .contentAsString - - val objectMapper = jacksonObjectMapper() - - val readValue = objectMapper.readValue(contentAsString) - - val expected = WebFinger( - subject = "acct:${user.name}@${user.domain}", - listOf( - WebFinger.Link( - "self", - "application/activity+json", - user.url - ) - ) - ) - - assertThat(readValue).isEqualTo(expected) - } - - @Test - fun `webfinger 存在しないacctを指定したとき404 Not Foundが返ってくる`() = runTest { - whenever( - webFingerApiService.findByNameAndDomain( - eq("fuga"), - eq("example.com") - ) - ).doThrow(UserNotFoundException::class) - - mockMvc.perform(get("/.well-known/webfinger?resource=acct:fuga@example.com")) - .andDo(print()) - .andExpect(status().isNotFound) - } - - @Test - fun `webfinger acctとして解釈できない場合は400 Bad Requestが返ってくる`() { - mockMvc.perform(get("/.well-known/webfinger?resource=@hello@aa@aab@aaa")) - .andDo(print()) - .andExpect(status().isBadRequest) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt deleted file mode 100644 index 9396a079..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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.activitypub.service.activity.accept - -import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.DynamicTest -import org.junit.jupiter.api.DynamicTest.dynamicTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestFactory -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.TestTransaction -import utils.UserBuilder -import java.net.URL - - -@ExtendWith(MockitoExtension::class) -class ApAcceptProcessorTest { - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var relationshipService: RelationshipService - - @Spy - private val transaction = TestTransaction - - @InjectMocks - private lateinit var apAcceptProcessor: ApAcceptProcessor - - @Test - fun `internalProcess objectがFollowの場合フォローを承認する`() = runTest { - - val json = """""" - val objectMapper = ActivityPubConfig().objectMapper() - val jsonNode = objectMapper.readTree(json) - - val accept = Accept( - apObject = Follow( - apObject = "https://example.com", - actor = "https://remote.example.com" - ), - actor = "https://example.com" - ) - val activity = ActivityPubProcessContext( - accept, jsonNode, HttpRequest( - URL("https://example.com"), - HttpHeaders(emptyMap()), HttpMethod.POST - ), null, true - ) - - val user = UserBuilder.localUserOf() - whenever(actorRepository.findByUrl(eq("https://example.com"))).doReturn(user) - val remoteUser = UserBuilder.remoteUserOf() - whenever(actorRepository.findByUrl(eq("https://remote.example.com"))).doReturn(remoteUser) - - apAcceptProcessor.internalProcess(activity) - - verify(relationshipService, times(1)).acceptFollowRequest(eq(user.id), eq(remoteUser.id), eq(false)) - } - - @Test - fun `internalProcess objectがFollow以外の場合IllegalActivityPubObjecExceptionが発生する`() = runTest { - val json = """""" - val objectMapper = ActivityPubConfig().objectMapper() - val jsonNode = objectMapper.readTree(json) - - val accept = Accept( - apObject = Like( - apObject = "https://example.com", - actor = "https://remote.example.com", - content = "", - id = "" - ), - actor = "https://example.com" - ) - val activity = ActivityPubProcessContext( - accept, jsonNode, HttpRequest( - URL("https://example.com"), - HttpHeaders(emptyMap()), HttpMethod.POST - ), null, true - ) - - assertThrows { - apAcceptProcessor.internalProcess(activity) - } - } - - @Test - fun `isSupproted Acceptにはtrue`() { - val actual = apAcceptProcessor.isSupported(ActivityType.Accept) - assertThat(actual).isTrue() - } - - @TestFactory - fun `isSupported Accept以外にはfalse`(): List { - return ActivityType - .values() - .filterNot { it == ActivityType.Accept } - .map { - dynamicTest("isSupported $it にはfalse") { - - val actual = apAcceptProcessor.isSupported(it) - assertThat(actual).isFalse() - } - } - } - - @Test - fun `type Acceptのclassjavaが返ってくる`() { - assertThat(apAcceptProcessor.type()).isEqualTo(Accept::class.java) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt deleted file mode 100644 index fbe5ffcf..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.service.follow.SendFollowDto -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import utils.UserBuilder -import java.net.URL - -class APSendFollowServiceImplTest { - @Test - fun `sendFollow フォローするユーザーのinboxにFollowオブジェクトが送られる`() = runTest { - val apRequestService = mock() - val applicationConfig = ApplicationConfig(URL("https://example.com")) - val apSendFollowServiceImpl = APSendFollowServiceImpl(apRequestService, applicationConfig) - - val sendFollowDto = SendFollowDto( - UserBuilder.localUserOf(), - UserBuilder.remoteUserOf() - ) - apSendFollowServiceImpl.sendFollow(sendFollowDto) - - val value = Follow( - apObject = sendFollowDto.followTargetActorId.url, - actor = sendFollowDto.actorId.url, - id = "${applicationConfig.url}/follow/${sendFollowDto.actorId.id}/${sendFollowDto.followTargetActorId.id}" - ) - verify(apRequestService, times(1)).apPost( - eq(sendFollowDto.followTargetActorId.inbox), - eq(value), - eq(sendFollowDto.actorId) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImplTest.kt deleted file mode 100644 index e8243f53..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImplTest.kt +++ /dev/null @@ -1,358 +0,0 @@ -/* - * 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.activitypub.service.common - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.util.Base64Util -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.sign.HttpSignatureSigner -import dev.usbharu.httpsignature.sign.Signature -import io.ktor.client.* -import io.ktor.client.engine.mock.* -import io.ktor.util.* -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import utils.UserBuilder -import java.net.URL -import java.security.MessageDigest -import java.time.format.DateTimeFormatter -import java.util.* - - -class APRequestServiceImplTest { - @Test - fun `apGet signerがnullのとき署名なしリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl( - HttpClient(MockEngine { - assertTrue(it.headers.contains("Date")) - assertTrue(it.headers.contains("Accept")) - assertFalse(it.headers.contains("Signature")) - assertDoesNotThrow { - dateTimeFormatter.parse(it.headers["Date"]) - } - respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") - }), - ActivityPubConfig().objectMapper(), - mock(), - dateTimeFormatter - ) - - val responseClass = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apGet("https://example.com", responseClass = responseClass::class.java) - } - - @Test - fun `apGet signerがnullではないがprivateKeyがnullのとき署名なしリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl( - HttpClient(MockEngine { - assertTrue(it.headers.contains("Date")) - assertTrue(it.headers.contains("Accept")) - assertFalse(it.headers.contains("Signature")) - assertDoesNotThrow { - dateTimeFormatter.parse(it.headers["Date"]) - } - respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") - }), - ActivityPubConfig().objectMapper(), - mock(), - dateTimeFormatter - ) - - val responseClass = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apGet( - "https://example.com", - UserBuilder.remoteUserOf(), - responseClass = responseClass::class.java - ) - } - - @Test - fun `apGet signerとprivatekeyがnullではないとき署名付きリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val httpSignatureSigner = mock { - onBlocking { - sign( - any(), - any(), - eq(listOf("(request-target)", "date", "host", "accept")) - ) - } doReturn Signature( - HttpRequest(URL("https://example.com"), HttpHeaders(mapOf()), HttpMethod.GET), "", "" - ) - } - val apRequestServiceImpl = APRequestServiceImpl( - HttpClient(MockEngine { - assertTrue(it.headers.contains("Date")) - assertTrue(it.headers.contains("Accept")) - assertTrue(it.headers.contains("Signature")) - assertDoesNotThrow { - dateTimeFormatter.parse(it.headers["Date"]) - } - respond("""{"type":"Follow","object": "https://example.com","actor": "https://example.com"}""") - }), - ActivityPubConfig().objectMapper(), - httpSignatureSigner, - dateTimeFormatter - ) - - val responseClass = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apGet( - "https://example.com", - UserBuilder.localUserOf( - privateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJhNETcFVoZW36\n" + - "pDiaaUDa1FsWGqULUa6jDWYbMXFirbbceJEfvaasac+E8VUQ3krrEhYBArntB1do\n" + - "1Zq/MpI97WaQefwrBmjJwjYglB8AHF1RRqFlJ0aABMBvuHiIzuTPv4dLS4+pJQWl\n" + - "iE9TKsxXgUrEdWLmpSukZpyiWnrgFtJ8322LXRuL9+O4ivns1JfozbrHTprI4ohe\n" + - "6taZJX1mhGBXQT+U/UrEILk+z70P2rrwxwerdO7s6nkkC3ieJWdi924/AopDlg12\n" + - "8udubLPbpWVVrHbSKviUr3VKBKGe4xmvO7hqpGwKmctaXRVPjh/ue2mCIzv3qyxQ\n" + - "3n2Xyhb3AgMBAAECggEAGddiSC/bg+ud0spER+i/XFBm7cq052KuFlKdiVcpxxGn\n" + - "pVYApiVXvjxDVDTuR5950/MZxz9mQDL0zoi1s1b00eQjhttdrta/kT/KWRslboo0\n" + - "nTuFbsc+jyQM2Ua6jjCZvto8qzchUPtiYfu80Floor/9qnuzFwiPNCHEbD1WDG4m\n" + - "fLuH+INnGY6eRF+pgly1dykGs18DaR3vC9CWOqR9PWH+p/myksVymR5adKauMc+l\n" + - "gjLaeB1YjnzXnHYLqwtCgh053kedPG/xZZwq48YNP5npSBIHsd9g8JIPVNOOc6+s\n" + - "bbFqD9aQQxG/WaA5hxHRupLkKGjE6lw4SnVYzKMZIQKBgQDryFa3qzJIBrCQQa0r\n" + - "6YlmZeeCQ8mQL8d0gY0Ixo9Gm2/9J71m/oBkhOqnS6Z5e5UHS5iVaqM7sIOZ2Ony\n" + - "kPADAtxUsk71Il+z+JgyN3OQ+DROLREi2TIWS523hbtN7e/fRFs7KoN6cH7IeF13\n" + - "3pphg9+WWRGX7y1zMd1puY/gSwKBgQDazFrAt/oZbnDhkX350OdIybz62OHNyuZv\n" + - "UX9fFl9i93SF+UhOpJ8YvDJtfLEJUkwO+V3TB+we1OlOYMTqir5M8GFn6YDotwxB\n" + - "r6eT886UpJgtJwswwwW2yaXo7zXaeg3ovRE8RJ4y++Mhuqeq3ajIo7xlhQjzBDEf\n" + - "ZAqasSWwhQKBgQC0VbUlo1XAywUOQH0/oc4KOJS6CDjJBBIsZM3G0X9SBJ7B5Dwz\n" + - "4yG2QAbtT6oTLldMjiA036vbgmUVLVe5w+sekniMexhy2wiRsOhPOCQ20+/Ffyil\n" + - "G7P4Y3tMm4cn0n1tqW2RsjF/Wz1M/OqYPPSc8uz2pEcVisSbX582Nsv5QwKBgEuy\n" + - "vAtFG6BE14UTIzSVFA/YzCs1choTAtqspZauVN4WoxffASdESU7zfbbnlxCUin/7\n" + - "wnxKl2SrYPSfAkHrMp/H4stivBjHi9QGA8JqbaR7tbKZeYOrVYTCC0alzEoERF+r\n" + - "WhUx4FHfV9vJikzRV53jGEE/X7NEVgJ4SDrw4wtJAoGAAMJ2kOIL3HSQPd8csXeU\n" + - "nkxLNzBsFpF76LVmLdzJttlr8HWBjLP/EJFQZFzuf5Hd38cLUOWWD3FRZVw0dUcN\n" + - "RSqfIYT4yDc/9GSRb6rOkdmBUWpTsrZjXBo0MC3p1QE6sNO8JfvmxHTSAe8apBh/\n" + - "gaYuQGh0lNa23HwwFoJxuoc=\n" + - "-----END PRIVATE KEY-----" - ), - responseClass = responseClass::class.java - ) - } - - @Test - fun `apPost bodyがnullでないときcontextにactivitystreamのURLを追加する`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - val readValue = ActivityPubConfig().objectMapper().readValue(it.body.toByteArray()) - - assertThat(readValue.context).containsAll(Constant.context) - - respondOk("{}") - }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter) - - val body = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apPost("https://example.com", body, null) - } - - @Test - fun `apPost bodyがnullのときリクエストボディは空`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - - assertEquals(0, it.body.toByteArray().size) - - respondOk("{}") - }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter) - - apRequestServiceImpl.apPost("https://example.com", null, null) - } - - @Test - fun `apPost signerがnullのとき署名なしリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - val src = it.body.toByteArray() - val readValue = ActivityPubConfig().objectMapper().readValue(src) - - assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams")) - - val map = it.headers.toMap() - assertThat(map).containsKey("Date") - .containsKey("Digest") - .containsKey("Accept") - .doesNotContainKey("Signature") - - assertDoesNotThrow { - dateTimeFormatter.parse(it.headers["Date"]) - } - val messageDigest = MessageDigest.getInstance("SHA-256") - val digest = Base64Util.encode(messageDigest.digest(src)) - - assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) - - respondOk("{}") - }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter) - - val body = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apPost("https://example.com", body, null) - } - - @Test - fun `apPost signerがnullではないがprivatekeyがnullのとき署名なしリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - val src = it.body.toByteArray() - val readValue = ActivityPubConfig().objectMapper().readValue(src) - - assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams")) - - val map = it.headers.toMap() - assertThat(map).containsKey("Date") - .containsKey("Digest") - .containsKey("Accept") - .doesNotContainKey("Signature") - - val messageDigest = MessageDigest.getInstance("SHA-256") - val digest = Base64Util.encode(messageDigest.digest(src)) - - assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) - - respondOk("{}") - }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter) - - val body = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apPost("https://example.com", body, UserBuilder.remoteUserOf()) - } - - @Test - fun `apPost signerがnullではないとき署名付きリクエストをする`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val httpSignatureSigner = mock { - onBlocking { - sign( - any(), - any(), - eq(listOf("(request-target)", "date", "host", "digest")) - ) - } doReturn Signature( - HttpRequest(URL("https://example.com"), HttpHeaders(mapOf()), HttpMethod.POST), "", "" - ) - } - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - val src = it.body.toByteArray() - val readValue = ActivityPubConfig().objectMapper().readValue(src) - - assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams")) - - val map = it.headers.toMap() - assertThat(map).containsKey("Date") - .containsKey("Digest") - .containsKey("Accept") - .containsKey("Signature") - - val messageDigest = MessageDigest.getInstance("SHA-256") - val digest = Base64Util.encode(messageDigest.digest(src)) - - assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last()) - - respondOk("{}") - }), ActivityPubConfig().objectMapper(), httpSignatureSigner, dateTimeFormatter) - - val body = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - apRequestServiceImpl.apPost( - "https://example.com", body, UserBuilder.localUserOf( - privateKey = "-----BEGIN PRIVATE KEY-----\n" + - "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1+pj+/t5WwU6P\n" + - "OiaAKfOHCUVMdOR5e2Jp0BUYfAFpim27pLsHRXVjdzs+D4gvDnQWC0FMltPyBldk\n" + - "gjisNMtTKgTTsYhlLlSi+yRDZvIQyH4b7xSX0hCeflTrTkt18ZldBRPfMHE0KSho\n" + - "mm3Lc7ubF32YzGoo3A3qEVDAR9dVQOnt/GXLiN4RHoStX+y5UiP6B4s49nyEwuLm\n" + - "+HE4ph3Loqn0dTEL4cEuI8ZX51J3mTKT3rmMo0wCXXOm8gD2Fu7hYEdr9ulWF8GO\n" + - "yVe7Miu9prbBlY/r4skdXc5o6uE8tsPT88Ly9lSr3xqbmn1/EhyqBRdcyoj28C65\n" + - "cThO38jvAgMBAAECggEAFbOaXkJ3smHgI/17zOnz1EU7QehovMIFlPfPJDnZk0QC\n" + - "XQ/CjBXw71kvM/H3PCFdn6lc8qzD/sdZ0a8j4glzu+m1ZKd1zBcv2bXYd79Fm9HF\n" + - "FEC5NHfFKpmHN/6AykJzFyA9Y+7reRx1aLAN6ubU1ySAgmHSQSgo8qJ4/k0y9UQS\n" + - "EbjxQL5ziXuxRBMn7InLUGLl5UfCC0V1R8MZQAe+fApKDXMQ0LHSJUg1A365PyhV\n" + - "seotqvhurHH3UVHf5n0/sFeqp2hI4ymR3cs4kd8IuNIXE7afh+89IyuVKMvJh+iQ\n" + - "ZGO1RL0v0mNtUpI81agSrrQ4LRBjSkP+5s5PdXTrSQKBgQD2lwMXLylhQzhRyhLx\n" + - "sSPRf9mKDUcretwA5Fh9GuAurKOz7SvIdzrUPFYUTUKSTwk8mVRRamkFtJ8IOB7Z\n" + - "MLenlFqxs4XrNGBcZxut5cPv68xn2F00Y4HwX9xmEi+vniNVrDpdVLxEoVfm1pBk\n" + - "02ZHCcfYVN0t8dnvXvlL+eJSqQKBgQC87GMoMvFnWgT23wdXtQH+F+gQAMUrkMWz\n" + - "Ld2uRwuSVQArgp+YgnwWMlYlFp/QIW90t7UVmf6bHIplO5bL2OwayIO1r/WxD1eN\n" + - "RLrFIeDbtCZWQTHUypnWtl+9lrh/RrCjZo/sZFl07OSIKgGM37j9taG6Nv6fV7gv\n" + - "T0q6eDCV1wKBgGh3CUQlIq6lv5JGvUfO95GlTA+EGIZ/Af0Ov74gSKD9Wky7STUf\n" + - "7bhD52OqZ218NjmJ64KiReO45TaiL89rKCLCYrmtiCpgggIjXEKLeDqH9ox3yOSM\n" + - "01t2APTs926629VLpV4sq6WXhJmyhHFybX3i0tr++MSiFOWnoo1hS1QhAoGAfVY6\n" + - "ppW9kDqppnrqrSZ6Lu//VnacWL3QW4JnWtLpe2iHF1auuQiAeF1mx25OEk/MWNvz\n" + - "+GPVBWUW7/hrn8vHQDGdJ/GYB6LNC/z4CAbk3f2TnY/dFnZfP5J4zBftSQtF7vIB\n" + - "M+yTaL4tE6UCqEpYuYFBzX/kxyP0Hvb09eb9HLsCgYEArFSgWpaLbADcWd+ygWls\n" + - "LNfch1Yl2bnqXKz1Dnw3J4l2gbVNcABXQLrB6upjtkytxj4ae66Sio7nf+dB5yJ6\n" + - "NVY7i4C0JrniY2OvLnuz2bKpaTgMPJxyZqGQ6Vu2b3x9WhcpiI83SCuCUgBKxjh/\n" + - "qEGv2ZqFfnNVrz5RXLHBoG4=\n" + - "-----END PRIVATE KEY-----" - ) - ) - } - - @Test - fun `apPost responseClassを指定した場合はjsonでシリアライズされる`() = runTest { - val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine { - val src = it.body.toByteArray() - val readValue = ActivityPubConfig().objectMapper().readValue(src) - - assertThat(readValue.context).contains(StringOrObject("https://www.w3.org/ns/activitystreams")) - - respondOk(src.decodeToString()) - }), ActivityPubConfig().objectMapper(), mock(), dateTimeFormatter) - - val body = Follow( - apObject = "https://example.com", - actor = "https://example.com" - ) - val actual = apRequestServiceImpl.apPost("https://example.com", body, null, body::class.java) - - assertThat(body).isEqualTo(actual) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt deleted file mode 100644 index 7c71a55a..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.resource.InMemoryCacheManager -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.UserBuilder -import dev.usbharu.hideout.activitypub.domain.model.objects.Object as APObject - -@ExtendWith(MockitoExtension::class) - -class APResourceResolveServiceImplTest { - - @Test - fun `単純な一回のリクエスト`() = runTest { - - - val actorRepository = mock() - - val user = UserBuilder.localUserOf() - whenever(actorRepository.findById(any())) doReturn user - - val apRequestService = mock { - onBlocking { - apGet( - eq("https"), - eq(user), - eq(APObject::class.java) - ) - } doReturn APObject( - emptyList() - ) - } - val apResourceResolveService = - APResourceResolveServiceImpl(apRequestService, actorRepository, InMemoryCacheManager()) - - apResourceResolveService.resolve("https", 0) - - verify(apRequestService, times(1)).apGet(eq("https"), eq(user), eq(APObject::class.java)) - } - - @Test - fun 複数回の同じリクエストが重複して発行されない() = runTest { - - - val actorRepository = mock() - - val user = UserBuilder.localUserOf() - whenever(actorRepository.findById(any())) doReturn user - - val apRequestService = mock { - onBlocking { - apGet( - eq("https"), - eq(user), - eq(APObject::class.java) - ) - } doReturn APObject( - emptyList() - ) - } - val apResourceResolveService = - APResourceResolveServiceImpl(apRequestService, actorRepository, InMemoryCacheManager()) - - apResourceResolveService.resolve("https", 0) - apResourceResolveService.resolve("https", 0) - apResourceResolveService.resolve("https", 0) - apResourceResolveService.resolve("https", 0) - - verify(apRequestService, times(1)).apGet( - eq("https"), - eq(user), - eq(APObject::class.java) - ) - } - - @Test - fun 複数回の同じリクエストが同時に発行されても重複して発行されない() = runTest { - - - val actorRepository = mock() - val user = UserBuilder.localUserOf() - - whenever(actorRepository.findById(any())) doReturn user - - - val apRequestService = mock { - onBlocking { - apGet( - eq("https"), - eq(user), - eq(APObject::class.java) - ) - } doReturn APObject( - emptyList() - ) - } - val apResourceResolveService = - APResourceResolveServiceImpl(apRequestService, actorRepository, InMemoryCacheManager()) - - repeat(10) { - awaitAll( - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - async { apResourceResolveService.resolve("https", 0) }, - ) - } - - verify(apRequestService, times(1)).apGet( - eq("https"), - eq(user), - eq(APObject::class.java) - ) - } - - @Test - fun 関係のないリクエストは発行する() = runTest { - - val actorRepository = mock() - - val user = UserBuilder.localUserOf() - whenever(actorRepository.findById(any())).doReturn( - user - ) - - val apRequestService = mock { - onBlocking { - apGet( - any(), - eq(user), - eq(APObject::class.java) - ) - } doReturn APObject( - emptyList() - ) - } - - val apResourceResolveService = - APResourceResolveServiceImpl(apRequestService, actorRepository, InMemoryCacheManager()) - - apResourceResolveService.resolve("abcd", 0) - apResourceResolveService.resolve("1234", 0) - apResourceResolveService.resolve("aaaa", 0) - - verify(apRequestService, times(3)).apGet( - any(), - eq(user), - eq(APObject::class.java) - ) - } - - -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APServiceImplTest.kt deleted file mode 100644 index b924baeb..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APServiceImplTest.kt +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.exception.JsonParseException -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.mockito.kotlin.mock -import utils.JsonObjectMapper.objectMapper -import kotlin.test.assertEquals - -class APServiceImplTest { - @Test - fun `parseActivity 正常なActivityをパースできる`() { - val apServiceImpl = APServiceImpl( - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - val activityType = apServiceImpl.parseActivity("""{"type": "Follow"}""") - - assertEquals(ActivityType.Follow, activityType) - } - - @Test - fun `parseActivity Typeが配列のActivityをパースできる`() { - val apServiceImpl = APServiceImpl( - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - val activityType = apServiceImpl.parseActivity("""{"type": ["Follow"]}""") - - assertEquals(ActivityType.Follow, activityType) - } - - @Test - fun `parseActivity Typeが配列で関係ない物が入っていてもパースできる`() { - val apServiceImpl = APServiceImpl( - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - val activityType = apServiceImpl.parseActivity("""{"type": ["Hello","Follow"]}""") - - assertEquals(ActivityType.Follow, activityType) - } - - @Test - fun `parseActivity jsonとして解釈できない場合JsonParseExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""hoge""") - } - } - - @Test - fun `parseActivity 空の場合JsonParseExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("") - } - } - - @Test - fun `parseActivity jsonにtypeプロパティがない場合JsonParseExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""{"actor": "https://example.com"}""") - } - } - - @Test - fun `parseActivity typeが配列でないときtypeが未定義の場合IllegalArgumentExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""{"type": "Hoge"}""") - } - } - - @Test - fun `parseActivity typeが配列のとき定義済みのtypeを見つけられなかった場合IllegalArgumentExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""{"type": ["Hoge","Fuga"]}""") - } - } - - @Test - fun `parseActivity typeが空の場合IllegalArgumentExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""{"type": ""}""") - } - } - - @Test - fun `parseActivity typeに指定されている文字の判定がcase-insensitiveで行われる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - val activityType = apServiceImpl.parseActivity("""{"type": "FoLlOw"}""") - - assertEquals(ActivityType.Follow, activityType) - } - - @Test - fun `parseActivity typeが配列のとき指定されている文字の判定がcase-insensitiveで行われる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - val activityType = apServiceImpl.parseActivity("""{"type": ["HoGE","fOllOw"]}""") - - assertEquals(ActivityType.Follow, activityType) - } - - @Test - fun `parseActivity activityがarrayのときJsonParseExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity("""[{"type": "Follow"},{"type": "Accept"}]""") - } - } - - @Test - fun `parseActivity activityがvalueのときJsonParseExceptionがthrowされる`() { - val apServiceImpl = APServiceImpl( - - objectMapper = objectMapper, owlProducer = mock() - ) - - //language=JSON - assertThrows { - apServiceImpl.parseActivity(""""hoge"""") - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt deleted file mode 100644 index 87bfbae4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ /dev/null @@ -1,308 +0,0 @@ -/* - * 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - -package dev.usbharu.hideout.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException -import dev.usbharu.hideout.activitypub.domain.model.Image -import dev.usbharu.hideout.activitypub.domain.model.Key -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.query.AnnounceQueryService -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.application.config.HtmlSanitizeConfig -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter -import dev.usbharu.hideout.core.service.post.PostService -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.plugins.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.client.utils.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.util.* -import io.ktor.util.date.* -import jakarta.validation.Validation -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.PostBuilder -import utils.UserBuilder -import java.time.Instant - -@ExtendWith(MockitoExtension::class) -class APNoteServiceImplTest { - - @Mock - private lateinit var postRepository: PostRepository - - @Mock - private lateinit var apUserService: APUserService - - @Mock - private lateinit var postService: PostService - - @Mock - private lateinit var apResourceResolverService: APResourceResolveService - - @Spy - private val postBuilder: Post.PostBuilder = Post.PostBuilder( - CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy()), - Validation.buildDefaultValidatorFactory().validator - ) - - @Mock - private lateinit var noteQueryService: NoteQueryService - - @Mock - private lateinit var mediaService: MediaService - - @Mock - private lateinit var emojiService: EmojiService - - @Mock - private lateinit var announceQueryService: AnnounceQueryService - - @InjectMocks - private lateinit var apNoteServiceImpl: APNoteServiceImpl - - @Test - fun `fetchNote(String,String) ノートが既に存在する場合はDBから取得したものを返す`() = runTest { - val url = "https://example.com/note" - val post = PostBuilder.of() - - val user = UserBuilder.localUserOf(id = post.actorId) - val expected = Note( - id = post.apId, - attributedTo = user.url, - content = post.text, - published = Instant.ofEpochMilli(post.createdAt).toString(), - to = listOfNotNull(public, user.followers), - sensitive = post.sensitive, - cc = listOfNotNull(public, user.followers), - inReplyTo = null - ) - - whenever(noteQueryService.findByApid(eq(url))).doReturn(expected to post) - - val actual = apNoteServiceImpl.fetchNote(url) - - assertEquals(expected, actual) - } - - @Test - fun `fetchNote(String,String) ノートがDBに存在しない場合リモートに取得しにいく`() = runTest { - val url = "https://example.com/note" - val post = PostBuilder.of() - - - val user = UserBuilder.localUserOf(id = post.actorId) - - val note = Note( - id = post.apId, - attributedTo = user.url, - content = post.text, - published = Instant.ofEpochMilli(post.createdAt).toString(), - to = listOfNotNull(public, user.followers), - sensitive = post.sensitive, - cc = listOfNotNull(public, user.followers), - inReplyTo = null - ) - - whenever(apResourceResolverService.resolve(eq(url), any(), isNull())).doReturn(note) - - whenever(noteQueryService.findByApid(eq(url))).doReturn(null) - - val person = Person( - name = user.name, - id = user.url, - preferredUsername = user.name, - summary = user.description, - inbox = user.inbox, - outbox = user.outbox, - url = user.url, - icon = Image( - type = emptyList(), - mediaType = "image/png", - url = user.url + "/icon.png" - ), - publicKey = Key( - id = user.keyId, - owner = user.url, - publicKeyPem = user.publicKey - ), - endpoints = mapOf("sharedInbox" to "https://example.com/inbox"), - followers = user.followers, - following = user.following, - manuallyApprovesFollowers = false - - ) - - whenever( - apUserService.fetchPersonWithEntity( - eq(note.attributedTo), - isNull(), - anyOrNull() - ) - ).doReturn(person to user) - - whenever(postRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId()) - - val actual = apNoteServiceImpl.fetchNote(url) - - assertEquals(note, actual) - } - - @OptIn(InternalAPI::class) - @Test - fun `fetchNote(String,String) ノートをリモートから取得した際にエラーが返ってきたらFailedToGetActivityPubResourceExceptionがthrowされる`() = - runTest { - val url = "https://example.com/note" - val responseData = HttpResponseData( - HttpStatusCode.BadRequest, - GMTDate(), - Headers.Empty, - HttpProtocolVersion.HTTP_1_1, - NullBody, - Dispatchers.IO - ) - whenever(apResourceResolverService.resolve(eq(url), any(), isNull())).doThrow( - ClientRequestException( - DefaultHttpResponse( - HttpClientCall( - HttpClient(), HttpRequestData( - Url("http://example.com"), - HttpMethod.Get, - Headers.Empty, - EmptyContent, - Job(null), - Attributes() - ), responseData - ), responseData - ), "" - ) - ) - - whenever(noteQueryService.findByApid(eq(url))).doReturn(null) - - assertThrows { apNoteServiceImpl.fetchNote(url) } - - } - - @Test - fun `fetchNote(Note,String) DBに無いNoteは保存される`() = runTest { - val user = UserBuilder.localUserOf() - val generateId = TwitterSnowflakeIdGenerateService.generateId() - val post = PostBuilder.of(id = generateId, userId = user.id) - - whenever(postRepository.generateId()).doReturn(generateId) - - val person = Person( - name = user.name, - id = user.url, - preferredUsername = user.name, - summary = user.name, - inbox = user.inbox, - outbox = user.outbox, - url = user.url, - icon = Image( - mediaType = "image/png", - url = user.url + "/icon.png" - ), - publicKey = Key( - id = user.keyId, - owner = user.url, - publicKeyPem = user.publicKey - ), - endpoints = mapOf("sharedInbox" to "https://example.com/inbox"), - following = user.following, - followers = user.followers - ) - - whenever(apUserService.fetchPersonWithEntity(eq(user.url), anyOrNull(), anyOrNull())).doReturn(person to user) - - whenever(noteQueryService.findByApid(eq(post.apId))).doReturn(null) - - val note = Note( - id = post.apId, - attributedTo = user.url, - content = post.text, - published = Instant.ofEpochMilli(post.createdAt).toString(), - to = listOfNotNull(public, user.followers), - sensitive = post.sensitive, - cc = listOfNotNull(public, user.followers), - inReplyTo = null - ) - - - val fetchNote = apNoteServiceImpl.fetchNote(note, null) - verify(postService, times(1)).createRemote( - eq( - PostBuilder.of( - id = generateId, userId = user.id, createdAt = post.createdAt - ) - ) - ) - assertEquals(note, fetchNote) - } - - @Test - fun `fetchNote DBに存在する場合取得して返す`() = runTest { - - val user = UserBuilder.localUserOf() - val post = PostBuilder.of(userId = user.id) - - val note = Note( - id = post.apId, - attributedTo = user.url, - content = post.text, - published = Instant.ofEpochMilli(post.createdAt).toString(), - to = listOfNotNull(public, user.followers), - sensitive = post.sensitive, - cc = listOfNotNull(public, user.followers), - inReplyTo = null - ) - - whenever(noteQueryService.findByApid(post.apId)).doReturn(note to post) - - val fetchNote = apNoteServiceImpl.fetchNote(note, null) - assertEquals(note, fetchNote) - } - - -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextDeserializerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextDeserializerTest.kt deleted file mode 100644 index 5a21e241..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextDeserializerTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.ap - -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.activitypub.domain.model.Follow - -class ContextDeserializerTest { - - @org.junit.jupiter.api.Test - fun deserialize() { - //language=JSON - val s = """ - { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "Hashtag": "as:Hashtag", - "quoteUrl": "as:quoteUrl", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "featured": "toot:featured", - "discoverable": "toot:discoverable", - "schema": "http://schema.org#", - "PropertyValue": "schema:PropertyValue", - "value": "schema:value", - "misskey": "https://misskey-hub.net/ns#", - "_misskey_content": "misskey:_misskey_content", - "_misskey_quote": "misskey:_misskey_quote", - "_misskey_reaction": "misskey:_misskey_reaction", - "_misskey_votes": "misskey:_misskey_votes", - "_misskey_talk": "misskey:_misskey_talk", - "isCat": "misskey:isCat", - "vcard": "http://www.w3.org/2006/vcard/ns#" - } - ], - "id": "https://test-misskey-v12.usbharu.dev/follows/9bg1zu54y7/9cydqvpjcn", - "type": "Follow", - "actor": "https://test-misskey-v12.usbharu.dev/users/9bg1zu54y7", - "object": "https://test-hideout.usbharu.dev/users/test3" -} - -""" - val readValue = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue(s) - println(readValue) - println(readValue.actor) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextSerializerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextSerializerTest.kt deleted file mode 100644 index 4500bc95..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/ap/ContextSerializerTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.ap - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import org.junit.jupiter.api.Test - -class ContextSerializerTest { - - @Test - fun serialize() { - val accept = Accept( - actor = "bbb", - apObject = Follow( - apObject = "ddd", - actor = "aaa" - ) - ) - val writeValueAsString = jacksonObjectMapper().writeValueAsString(accept) - println(writeValueAsString) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtensionKtTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtensionKtTest.kt deleted file mode 100644 index 6e57f239..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtensionKtTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class ExposedPaginationExtensionKtTest { - - @BeforeEach - fun setUp(): Unit = transaction { - val map = (1..100).map { it to it.toString() } - - ExposePaginationTestTable.batchInsert(map){ - this[ExposePaginationTestTable.id] = it.first.toLong() - this[ExposePaginationTestTable.name] = it.second - } - } - - @AfterEach - fun tearDown():Unit = transaction { - ExposePaginationTestTable.deleteAll() - } - - @Test - fun パラメーター無しでの取得(): Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(100) - assertThat(pagination.prev).isEqualTo(81) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(100) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(81) - assertThat(pagination).size().isEqualTo(20) - } - - @Test - fun maxIdを指定して取得(): Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(maxId = 100), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(99) - assertThat(pagination.prev).isEqualTo(80) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(99) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(80) - assertThat(pagination).size().isEqualTo(20) - } - - @Test - fun sinceIdを指定して取得(): Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(sinceId = 15), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(100) - assertThat(pagination.prev).isEqualTo(81) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(100) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(81) - assertThat(pagination).size().isEqualTo(20) - } - - @Test - fun minIdを指定して取得():Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(minId = 45), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(65) - assertThat(pagination.prev).isEqualTo(46) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(65) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(46) - assertThat(pagination).size().isEqualTo(20) - } - - @Test - fun maxIdとsinceIdを指定して取得(): Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(maxId = 45, sinceId = 34), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(44) - assertThat(pagination.prev).isEqualTo(35) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(44) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(35) - assertThat(pagination).size().isEqualTo(10) - } - - @Test - fun maxIdとminIdを指定して取得():Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().limit(20).withPagination(Page.of(maxId = 54, minId = 45), ExposePaginationTestTable.id) - - assertThat(pagination.next).isEqualTo(53) - assertThat(pagination.prev).isEqualTo(46) - assertThat(pagination.first()[ExposePaginationTestTable.id]).isEqualTo(53) - assertThat(pagination.last()[ExposePaginationTestTable.id]).isEqualTo(46) - assertThat(pagination).size().isEqualTo(8) - } - - @Test - fun limitを指定して取得():Unit = transaction { - val pagination: PaginationList = ExposePaginationTestTable.selectAll().withPagination(Page.of(limit = 30), ExposePaginationTestTable.id) - assertThat(pagination).size().isEqualTo(30) - } - - @Test - fun 結果が0件の場合はprevとnextがnullになる():Unit = transaction { - val pagination = ExposePaginationTestTable.selectAll().where { ExposePaginationTestTable.id.isNull() } - .withPagination(Page.of(), ExposePaginationTestTable.id) - - assertThat(pagination).isEmpty() - assertThat(pagination.next).isNull() - assertThat(pagination.prev).isNull() - } - - object ExposePaginationTestTable : Table(){ - val id = long("id") - val name = varchar("name",100) - - override val primaryKey: PrimaryKey - get() = PrimaryKey(id) - } - - companion object { - private lateinit var database: Database - - @JvmStatic - @BeforeAll - fun beforeAll(): Unit { - database = Database.connect( - url = "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;", - driver = "org.h2.Driver", - user = "sa", - password = "" - ) - - transaction(database) { - SchemaUtils.create(ExposePaginationTestTable) - SchemaUtils.createMissingTablesAndColumns(ExposePaginationTestTable) - } - } - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PageTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PageTest.kt deleted file mode 100644 index a0c4bba7..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PageTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -class PageTest { - @Test - fun minIdが指定されているとsinceIdは無視される() { - val page = Page.of(1, 2, 3, 4) - - assertThat(page.maxId).isEqualTo(1) - assertThat(page.sinceId).isNull() - assertThat(page.minId).isEqualTo(3) - assertThat(page.limit).isEqualTo(4) - } - - @Test - fun minIdがnullのときはsinceIdが使われる() { - val page = Page.of(1, 2, null, 4) - - assertThat(page.maxId).isEqualTo(1) - assertThat(page.minId).isNull() - assertThat(page.sinceId).isEqualTo(2) - assertThat(page.limit).isEqualTo(4) - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationListKtTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationListKtTest.kt deleted file mode 100644 index af4db171..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/PaginationListKtTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.application.infrastructure.exposed - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -class PaginationListKtTest { - @Test - fun `toHttpHeader nextとprevがnullでない場合両方作成される`() { - val paginationList = PaginationList(emptyList(), 1, 2) - - val httpHeader = - paginationList.toHttpHeader({ "https://example.com?max_id=$it" }, { "https://example.com?min_id=$it" }) - - assertThat(httpHeader).isEqualTo("; rel=\"next\", ; rel=\"prev\"") - } - - @Test - fun `toHttpHeader nextがnullなら片方だけ作成される`() { - val paginationList = PaginationList(emptyList(), 1,null) - - val httpHeader = - paginationList.toHttpHeader({ "https://example.com?max_id=$it" }, { "https://example.com?min_id=$it" }) - - assertThat(httpHeader).isEqualTo("; rel=\"next\"") - } - - @Test - fun `toHttpHeader prevがnullなら片方だけ作成される`() { - val paginationList = PaginationList(emptyList(), null,2) - - val httpHeader = - paginationList.toHttpHeader({ "https://example.com?max_id=$it" }, { "https://example.com?min_id=$it" }) - - assertThat(httpHeader).isEqualTo("; rel=\"prev\"") - } - - @Test - fun `toHttpHeader 両方nullならnullが返ってくる`() { - val paginationList = PaginationList(emptyList(), null, null) - - - val httpHeader = - paginationList.toHttpHeader({ "https://example.com?max_id=$it" }, { "https://example.com?min_id=$it" }) - - assertThat(httpHeader).isNull() - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateServiceTest.kt deleted file mode 100644 index eb09683c..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/id/TwitterSnowflakeIdGenerateServiceTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.application.service.id - -// import kotlinx.coroutines.NonCancellable.message -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class TwitterSnowflakeIdGenerateServiceTest { - @Test - fun noDuplicateTest() = runBlocking { - val mutex = Mutex() - val mutableListOf = mutableListOf() - coroutineScope { - repeat(500000) { - launch(Dispatchers.IO) { - val id = TwitterSnowflakeIdGenerateService.generateId() - mutex.withLock { - mutableListOf.add(id) - } - } - } - } - - assertEquals(0, mutableListOf.size - mutableListOf.toSet().size) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImplTest.kt deleted file mode 100644 index bba96791..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/MetaServiceImplTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package dev.usbharu.hideout.application.service.init - -import dev.usbharu.hideout.core.domain.exception.NotInitException -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.Meta -import dev.usbharu.hideout.core.domain.model.meta.MetaRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.mockito.kotlin.* -import utils.TestTransaction -import java.util.* -import kotlin.test.assertEquals - -class MetaServiceImplTest { - @Test - fun `getMeta メタデータを取得できる`() = runTest { - val meta = Meta("1.0.0", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda")) - val metaRepository = mock { - onBlocking { get() } doReturn meta - } - val metaService = MetaServiceImpl(metaRepository, TestTransaction) - val actual = metaService.getMeta() - assertEquals(meta, actual) - } - - @Test - fun `getMeta メタデータが無いときはNotInitExceptionがthrowされる`() = runTest { - val metaRepository = mock { - onBlocking { get() } doReturn null - } - val metaService = MetaServiceImpl(metaRepository, TestTransaction) - assertThrows { metaService.getMeta() } - } - - @Test - fun `updateMeta メタデータを保存できる`() = runTest { - val meta = Meta("1.0.1", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda")) - val metaRepository = mock { - onBlocking { save(any()) } doReturn Unit - } - val metaServiceImpl = MetaServiceImpl(metaRepository, TestTransaction) - metaServiceImpl.updateMeta(meta) - argumentCaptor { - verify(metaRepository).save(capture()) - assertEquals(meta, firstValue) - } - } - - @Test - fun `getJwtMeta Jwtメタデータを取得できる`() = runTest { - val meta = Meta("1.0.0", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda")) - val metaRepository = mock { - onBlocking { get() } doReturn meta - } - val metaService = MetaServiceImpl(metaRepository, TestTransaction) - val actual = metaService.getJwtMeta() - assertEquals(meta.jwt, actual) - } - - @Test - fun `getJwtMeta メタデータが無いときはNotInitExceptionがthrowされる`() = runTest { - val metaRepository = mock { - onBlocking { get() } doReturn null - } - val metaService = MetaServiceImpl(metaRepository, TestTransaction) - assertThrows { metaService.getJwtMeta() } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImplTest.kt deleted file mode 100644 index d24bc17c..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/application/service/init/ServerInitialiseServiceImplTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package dev.usbharu.hideout.application.service.init - -import dev.usbharu.hideout.core.domain.model.meta.Jwt -import dev.usbharu.hideout.core.domain.model.meta.Meta -import dev.usbharu.hideout.core.domain.model.meta.MetaRepository -import dev.usbharu.hideout.util.ServerUtil -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.mockito.kotlin.* -import utils.TestTransaction -import java.util.* -import kotlin.test.assertEquals - -class ServerInitialiseServiceImplTest { - @Test - fun `init メタデータが無いときに初期化を実行する`() = runTest { - val metaRepository = mock { - onBlocking { get() } doReturn null - onBlocking { save(any()) } doReturn Unit - } - val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction) - - serverInitialiseServiceImpl.init() - verify(metaRepository, times(1)).save(any()) - } - - @Test - fun `init メタデータが存在して同じバージョンのときは何もしない`() = runTest { - val meta = Meta(ServerUtil.getImplementationVersion(), Jwt(UUID.randomUUID(), "aaafafd", "afafasdf")) - val metaRepository = mock { - onBlocking { get() } doReturn meta - } - val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction) - serverInitialiseServiceImpl.init() - verify(metaRepository, times(0)).save(any()) - } - - @Test - fun `init メタデータが存在して違うバージョンのときはバージョンを変更する`() = runTest { - val meta = Meta("1.0.0", Jwt(UUID.randomUUID(), "aaafafd", "afafasdf")) - val metaRepository = mock { - onBlocking { get() } doReturn meta - onBlocking { save(any()) } doReturn Unit - } - - val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction) - serverInitialiseServiceImpl.init() - verify(metaRepository, times(1)).save(any()) - argumentCaptor { - verify(metaRepository, times(1)).save(capture()) - assertEquals(ServerUtil.getImplementationVersion(), firstValue.version) - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescriptionTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescriptionTest.kt new file mode 100644 index 00000000..acfa8b11 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescriptionTest.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.core.domain.model.actor + +class ActorDescriptionTest \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorIdTest.kt similarity index 60% rename from hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorTest.kt rename to hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorIdTest.kt index 573dd862..12c57c0c 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorIdTest.kt @@ -1,13 +1,12 @@ package dev.usbharu.hideout.core.domain.model.actor import org.junit.jupiter.api.Test -import utils.UserBuilder -class ActorTest { +class ActorIdTest { @Test - fun validator() { + fun idを負の数にすることはできない() { org.junit.jupiter.api.assertThrows { - UserBuilder.localUserOf(name = "うんこ") + ActorId(-1) } } } \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorNameTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorNameTest.kt new file mode 100644 index 00000000..477f799d --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorNameTest.kt @@ -0,0 +1,35 @@ +package dev.usbharu.hideout.core.domain.model.actor + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +class ActorNameTest { + @Test + fun blankはダメ() { + assertThrows { + ActorName("") + } + } + + @Test + fun 長過ぎるとダメ() { + assertThrows { + ActorName("a".repeat(1000)) + } + } + + @Test + fun 指定外の文字は使えない() { + assertThrows { + ActorName("あ") + } + } + + @Test + fun 普通に作成できる() { + assertDoesNotThrow { + ActorName("test-user") + } + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKeyTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKeyTest.kt new file mode 100644 index 00000000..37bb1938 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPublicKeyTest.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.core.domain.model.actor + +class ActorPublicKeyTest \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenNameTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenNameTest.kt new file mode 100644 index 00000000..163dbaf3 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenNameTest.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout.core.domain.model.actor + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ActorScreenNameTest { + @Test + fun screenNameがlengthを超えると無視される() { + val actorScreenName = ActorScreenName("a".repeat(1000)) + + assertEquals(ActorScreenName.length, actorScreenName.screenName.length) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt new file mode 100644 index 00000000..a8a67f4d --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt @@ -0,0 +1,130 @@ +package dev.usbharu.hideout.core.domain.model.actor + +import dev.usbharu.hideout.core.domain.event.actor.ActorEvent +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import utils.AssertDomainEvent.assertContainsEvent +import utils.AssertDomainEvent.assertEmpty +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull + +class ActorsTest { + @Test + fun suspendがtrueのときactorSuspendイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + actor.suspend = true + + assertContainsEvent(actor, ActorEvent.ACTOR_SUSPEND.eventName) + } + + @Test + fun suspendがfalseになったときactorUnsuspendイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey(""), suspend = true) + + actor.suspend = false + + assertContainsEvent(actor, ActorEvent.ACTOR_UNSUSPEND.eventName) + } + + @Test + fun alsoKnownAsに自分自身が含まれない場合更新される() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + val actorIds = setOf(ActorId(100), ActorId(200)) + actor.alsoKnownAs = actorIds + + assertEquals(actorIds, actor.alsoKnownAs) + } + + @Test + fun moveToに自分自身が設定された場合moveイベントが発生し更新される() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + + actor.moveTo = ActorId(100) + + assertContainsEvent(actor, ActorEvent.MOVE.eventName) + } + + @Test + fun alsoKnownAsに自分自身が含まれてはいけない() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + assertThrows { + actor.alsoKnownAs = setOf(actor.id) + } + } + + @Test + fun moveToに自分自身が設定されてはいけない() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + assertThrows { + actor.moveTo = actor.id + } + } + + @Test + fun descriptionが更新されたときupdateイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + actor.description = ActorDescription("hoge fuga") + + assertContainsEvent(actor, ActorEvent.UPDATE.eventName) + } + + @Test + fun screenNameが更新されたときupdateイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + actor.screenName = ActorScreenName("fuga hoge") + + assertContainsEvent(actor, ActorEvent.UPDATE.eventName) + } + + @Test + fun deleteが実行されたときすでにdeletedがtrueなら何もしない() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey(""), deleted = true) + + actor.delete() + + assertEmpty(actor) + } + + @Test + fun deleteが実行されたときdeletedがfalseならdeleteイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + actor.delete() + + assertEquals(ActorScreenName.empty, actor.screenName) + assertEquals(ActorDescription.empty, actor.description) + assertEquals(emptySet(), actor.emojis) + assertNull(actor.lastPostAt) + assertEquals(ActorPostsCount.ZERO, actor.postsCount) + assertNull(actor.followersCount) + assertNull(actor.followingCount) + assertContainsEvent(actor, ActorEvent.DELETE.eventName) + } + + @Test + fun restoreが実行されたときcheckUpdateイベントが発生する() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey(""), deleted = true) + + actor.restore() + + assertFalse(actor.deleted) + assertContainsEvent(actor, ActorEvent.CHECK_UPDATE.eventName) + } + + @Test + fun checkUpdateが実行されたときcheckUpdateイベントがh() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + actor.checkUpdate() + + assertContainsEvent(actor, ActorEvent.CHECK_UPDATE.eventName) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt new file mode 100644 index 00000000..0fb9087d --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt @@ -0,0 +1,78 @@ +package dev.usbharu.hideout.core.domain.model.actor + +import dev.usbharu.hideout.core.domain.model.emoji.EmojiId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.shared.Domain +import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService +import kotlinx.coroutines.runBlocking +import java.net.URI +import java.time.Instant + +object TestActorFactory { + private val idGenerateService = TwitterSnowflakeIdGenerateService + + fun create( + id: Long = generateId(), + actorName: String = "test-$id", + domain: String = "example.com", + actorScreenName: String = actorName, + description: String = "test description", + inbox: URI = URI.create("https://example.com/$id/inbox"), + outbox: URI = URI.create("https://example.com/$id/outbox"), + uri: URI = URI.create("https://example.com/$id"), + publicKey: ActorPublicKey = ActorPublicKey(""), + privateKey: ActorPrivateKey? = null, + createdAt: Instant = Instant.now(), + keyId: String = "https://example.com/$id#key-id", + followersEndpoint: URI = URI.create("https://example.com/$id/followers"), + followingEndpoint: URI = URI.create("https://example.com/$id/following"), + instanceId: Long = 1L, + locked: Boolean = false, + followersCount: Int = 0, + followingCount: Int = 0, + postCount: Int = 0, + lastPostDate: Instant? = null, + suspend: Boolean = false, + alsoKnownAs: Set = emptySet(), + moveTo: ActorId? = null, + emojiIds: Set = emptySet(), + deleted: Boolean = false, + ): Actor { + return runBlocking { + Actor( + id = ActorId(id), + name = ActorName(actorName), + domain = Domain(domain), + screenName = ActorScreenName(actorScreenName), + description = ActorDescription(description), + inbox = inbox, + outbox = outbox, + url = uri, + publicKey = publicKey, + privateKey = privateKey, + createdAt = createdAt, + keyId = ActorKeyId(keyId), + followersEndpoint = followersEndpoint, + followingEndpoint = followingEndpoint, + instance = InstanceId(instanceId), + locked = locked, + followersCount = ActorRelationshipCount(followersCount), + followingCount = ActorRelationshipCount(followingCount), + postsCount = ActorPostsCount(postCount), + lastPostAt = lastPostDate, + suspend = suspend, + alsoKnownAs = alsoKnownAs, + moveTo = moveTo, + emojiIds = emojiIds, + deleted = deleted, + roles = emptySet(), + icon = null, + banner = null + ) + } + } + + private fun generateId(): Long = runBlocking { + idGenerateService.generateId() + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/PostTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/PostTest.kt new file mode 100644 index 00000000..c881d6e4 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/PostTest.kt @@ -0,0 +1,264 @@ +package dev.usbharu.hideout.core.domain.model.post + +import dev.usbharu.hideout.core.domain.event.post.PostEvent +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey +import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import utils.AssertDomainEvent.assertContainsEvent +import utils.AssertDomainEvent.assertEmpty +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class PostTest { + @Test + fun deletedがtrueのときghostのidが返される() { + val post = TestPostFactory.create(deleted = true) + + assertEquals(ActorId.ghost, post.actorId) + } + + @Test + fun deletedがfalseの時actorのIDが返される() { + val post = TestPostFactory.create(deleted = false, actorId = 100) + + assertEquals(ActorId(100), post.actorId) + } + + @Test + fun visibilityがDIRECTのとき変更できない() { + val post = TestPostFactory.create(visibility = Visibility.DIRECT) + + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + + assertThrows { + post.setVisibility(Visibility.PUBLIC, actor) + } + assertThrows { + post.setVisibility(Visibility.UNLISTED, actor) + } + assertThrows { + post.setVisibility(Visibility.FOLLOWERS, actor) + } + } + + @Test + fun visibilityを小さくすることはできないPUBLIC() { + val post = TestPostFactory.create(visibility = Visibility.PUBLIC) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibility(Visibility.DIRECT, actor) + } + assertThrows { + post.setVisibility(Visibility.UNLISTED, actor) + } + assertThrows { + post.setVisibility(Visibility.FOLLOWERS, actor) + } + } + + @Test + fun visibilityを小さくすることはできないUNLISTED() { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibility(Visibility.DIRECT, actor) + } + assertThrows { + post.setVisibility(Visibility.FOLLOWERS, actor) + } + } + + @Test + fun visibilityを小さくすることはできないFOLLOWERS() { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibility(Visibility.DIRECT, actor) + } + } + + @Test + fun visibilityをDIRECTにあとからすることはできない() { + val post = TestPostFactory.create(visibility = Visibility.DIRECT) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibility(Visibility.DIRECT, actor) + } + } + + @Test + fun visibilityを大きくすることができるFOLLOWERS() { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertDoesNotThrow { + post.setVisibility(Visibility.UNLISTED, actor) + } + + val post2 = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + + assertDoesNotThrow { + post2.setVisibility(Visibility.PUBLIC, actor) + } + } + + @Test + fun visibilityを大きくすることができるUNLISTED() { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertDoesNotThrow { + post.setVisibility(Visibility.PUBLIC, actor) + } + } + + @Test + fun deletedがtrueのときvisibilityを変更できない() { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED, deleted = true) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibility(Visibility.PUBLIC, actor) + } + } + + @Test + fun visibilityが変更されない限りドメインイベントは発生しない() { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibility(Visibility.UNLISTED, actor) + assertEmpty(post) + + } + + @Test + fun visibilityが変更されるとupdateイベントが発生する() { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibility(Visibility.PUBLIC, actor) + + assertContainsEvent(post, PostEvent.UPDATE.eventName) + } + + @Test + fun deletedがtrueのときvisibleActorsを変更できない() { + val post = TestPostFactory.create(deleted = true) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setVisibleActors(setOf(ActorId(100)), actor) + } + } + + @Test + fun ゔvisibilityがDIRECT以外の時visibleActorsを変更できない() { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibleActors(setOf(ActorId(100)), actor) + assertEmpty(post) + + val post2 = TestPostFactory.create(visibility = Visibility.UNLISTED) + + post2.setVisibleActors(setOf(ActorId(100)), actor) + assertEmpty(post2) + + val post3 = TestPostFactory.create(visibility = Visibility.PUBLIC) + + post3.setVisibleActors(setOf(ActorId(100)), actor) + assertEmpty(post3) + } + + @Test + fun visibilityがDIRECTの時visibleActorsを変更できる() { + val post = TestPostFactory.create(visibility = Visibility.DIRECT) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibleActors(setOf(ActorId(100)), actor) + assertEquals(setOf(ActorId(100)), post.visibleActors) + } + + @Test + fun visibleActorsから削除されることはない() { + val post = TestPostFactory.create(visibility = Visibility.DIRECT, visibleActors = listOf(100)) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibleActors(setOf(ActorId(200)), actor) + assertEquals(setOf(ActorId(100), ActorId(200)), post.visibleActors) + } + + @Test + fun visibleActorsに追加された時updateイベントが発生する() { + val post = TestPostFactory.create(visibility = Visibility.DIRECT) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setVisibleActors(setOf(ActorId(100)), actor) + + assertContainsEvent(post, PostEvent.UPDATE.eventName) + } + + @Test + fun hideがtrueのときcontentがemptyを返す() { + val post = TestPostFactory.create(hide = true) + + assertEquals(PostContent.empty, post.content) + } + + @Test + fun deletedがtrueの時contentをセットできない() { + val post = TestPostFactory.create(deleted = true) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setContent(PostContent("test", "test", emptyList()), actor) + } + } + + @Test + fun contentの内容が変更されたらupdateイベントが発生する() { + val post = TestPostFactory.create() + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setContent(PostContent("test", "test", emptyList()), actor) + assertContainsEvent(post, PostEvent.UPDATE.eventName) + } + + @Test + fun hideがtrueの時nullを返す() { + val post = TestPostFactory.create(hide = true, overview = "aaaa") + + assertNull(post.overview) + } + + @Test + fun hideがfalseの時overviewを返す() { + val post = TestPostFactory.create(hide = false, overview = "aaaa") + + assertEquals(PostOverview("aaaa"), post.overview) + } + + @Test + fun deletedがtrueのときセットできない() { + val post = TestPostFactory.create(deleted = true) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + assertThrows { + post.setOverview(PostOverview("aaaa"), actor) + } + } + + @Test + fun deletedがfalseのときセットできる() { + val post = TestPostFactory.create(deleted = false) + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + val overview = PostOverview("aaaa") + assertDoesNotThrow { + post.setOverview(overview, actor) + } + assertEquals(overview, post.overview) + + assertContainsEvent(post, PostEvent.UPDATE.eventName) + } + + @Test + fun overviewの内容が更新されなかった時イベントが発生しない() { + val post = TestPostFactory.create(overview = "aaaa") + val actor = TestActorFactory.create(id = post.actorId.id, publicKey = ActorPublicKey("")) + post.setOverview(PostOverview("aaaa"), actor) + assertEmpty(post) + } + + +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt new file mode 100644 index 00000000..2bf9c5ae --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt @@ -0,0 +1,54 @@ +package dev.usbharu.hideout.core.domain.model.post + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.media.MediaId +import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService +import kotlinx.coroutines.runBlocking +import java.net.URI +import java.time.Instant + +object TestPostFactory { + private val idGenerateService = TwitterSnowflakeIdGenerateService + + fun create( + id: Long = generateId(), + actorId: Long = 1, + overview: String? = null, + content: String = "This is test content", + createdAt: Instant = Instant.now(), + visibility: Visibility = Visibility.PUBLIC, + url: URI = URI.create("https://example.com/$actorId/posts/$id"), + repostId: Long? = null, + replyId: Long? = null, + sensitive: Boolean = false, + apId: URI = URI.create("https://example.com/$actorId/posts/$id"), + deleted: Boolean = false, + mediaIds: List = emptyList(), + visibleActors: List = emptyList(), + hide: Boolean = false, + moveTo: Long? = null, + ): Post { + return Post( + PostId(id), + ActorId(actorId), + overview = overview?.let { PostOverview(it) }, + content = PostContent(content, content, emptyList()), + createdAt = createdAt, + visibility = visibility, + url = url, + repostId = repostId?.let { PostId(it) }, + replyId?.let { PostId(it) }, + sensitive = sensitive, + apId = apId, + deleted = deleted, + mediaIds.map { MediaId(it) }, + visibleActors.map { ActorId(it) }.toSet(), + hide = hide, + moveTo?.let { PostId(it) } + ) + } + + private fun generateId(): Long = runBlocking { + idGenerateService.generateId() + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt new file mode 100644 index 00000000..0b7acd6d --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt @@ -0,0 +1,41 @@ +package dev.usbharu.hideout.core.domain.service.actor + +import dev.usbharu.hideout.core.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey +import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory +import org.junit.jupiter.api.Test +import java.net.URI +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RemoteActorCheckDomainServiceTest { + @Test + fun リモートのドメインならtrueを返す() { + val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) + + val remoteActor = RemoteActorCheckDomainService( + ApplicationConfig( + URI.create("https://local.example.com").toURL() + ) + ).isRemoteActor( + actor + ) + + assertTrue(remoteActor) + } + + @Test + fun ローカルのActorならfalseを返す() { + val actor = TestActorFactory.create(domain = "local.example.com", publicKey = ActorPublicKey("")) + + val localActor = RemoteActorCheckDomainService( + ApplicationConfig( + URI.create("https://local.example.com").toURL() + ) + ).isRemoteActor( + actor + ) + + assertFalse(localActor) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderCheckerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderCheckerTest.kt deleted file mode 100644 index 4a150e10..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureHeaderCheckerTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.util.Base64Util -import org.intellij.lang.annotations.Language -import org.junit.jupiter.api.Assertions.assertDoesNotThrow -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import java.net.URI -import java.security.MessageDigest -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.* - -class HttpSignatureHeaderCheckerTest { - - val format = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) - - @Test - fun `checkDate 未来はダメ`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("http://example.com").toURL())) - - val s = ZonedDateTime.now().plusDays(1).format(format) - - assertThrows { - httpSignatureHeaderChecker.checkDate(s) - } - } - - - @Test - fun `checkDate 過去はOK`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("http://example.com").toURL())) - - val s = ZonedDateTime.now().minusHours(1).format(format) - - assertDoesNotThrow { - httpSignatureHeaderChecker.checkDate(s) - } - } - - @Test - fun `checkDate 86400秒以上昔はダメ`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("http://example.com").toURL())) - - val s = ZonedDateTime.now().minusSeconds(86401).format(format) - - assertThrows { - httpSignatureHeaderChecker.checkDate(s) - } - } - - @Test - fun `checkHost 大文字小文字の違いはセーフ`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("https://example.com").toURL())) - - assertDoesNotThrow { - httpSignatureHeaderChecker.checkHost("example.com") - httpSignatureHeaderChecker.checkHost("EXAMPLE.COM") - } - } - - @Test - fun `checkHost サブドメインはダメ`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("https://example.com").toURL())) - - assertThrows { - httpSignatureHeaderChecker.checkHost("follower.example.com") - } - } - - @Test - fun `checkDigest リクエストボディが同じなら何もしない`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("https://example.com").toURL())) - - - val sha256 = MessageDigest.getInstance("SHA-256") - - @Language("JSON") val requestBody = """{"@context":"","type":"hoge"}""" - - val digest = Base64Util.encode(sha256.digest(requestBody.toByteArray())) - - assertDoesNotThrow { - httpSignatureHeaderChecker.checkDigest(requestBody.toByteArray(), "SHA-256=" + digest) - } - } - - @Test - fun `checkDigest リクエストボディがちょっとでも違うとダメ`() { - val httpSignatureHeaderChecker = - HttpSignatureHeaderChecker(ApplicationConfig(URI.create("https://example.com").toURL())) - - - val sha256 = MessageDigest.getInstance("SHA-256") - - @Language("JSON") val requestBody = """{"type":"hoge","@context":""}""" - @Language("JSON") val requestBody2 = """{"@context":"","type":"hoge"}""" - val digest = Base64Util.encode(sha256.digest(requestBody.toByteArray())) - - assertThrows { - httpSignatureHeaderChecker.checkDigest(requestBody2.toByteArray(), digest) - } - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImplTest.kt deleted file mode 100644 index b6ae6af4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImplTest.kt +++ /dev/null @@ -1,404 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterAction -import dev.usbharu.hideout.core.domain.model.filter.FilterMode -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import utils.PostBuilder - -class MuteProcessServiceImplTest { - @Test - fun 単純な文字列にマッチする() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mute", - FilterMode.NONE - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute")) - } - - @Test - fun 複数の文字列でマッチする() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mate", - FilterMode.NONE - ), - FilterKeyword( - 1, - 1, - "mata", - FilterMode.NONE - ), - FilterKeyword( - 1, - 1, - "mute", - FilterMode.NONE - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute")) - } - - @Test - fun 単語にマッチする() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mute", - FilterMode.WHOLE_WORD - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute")) - } - - @Test - fun 単語以外にはマッチしない() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mutetest") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mute", - FilterMode.WHOLE_WORD - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isNull() - } - - @Test - fun 複数の単語にマッチする() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mate", - FilterMode.WHOLE_WORD - ), - FilterKeyword( - 1, - 1, - "mata", - FilterMode.WHOLE_WORD - ), - FilterKeyword( - 1, - 1, - "mute", - FilterMode.WHOLE_WORD - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute")) - } - - @Test - fun 正規表現も使える() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "e\\st", - FilterMode.REGEX - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "e t")) - } - - @Test - fun cw文字にマッチする() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(overview = "mute test", text = "hello") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "e\\st", - FilterMode.REGEX - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "e t")) - } - - @Test - fun 文字列と単語と正規表現を同時に使える() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "e\\st", - FilterMode.REGEX - ), - FilterKeyword( - 2, - 1, - "mute", - FilterMode.NONE - ), - FilterKeyword( - 3, - 1, - "test", - FilterMode.WHOLE_WORD - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isEqualTo(FilterResult(filterQueryModel, "mute")) - } - - @Test - fun 複数の投稿を処理できる() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "mute", - FilterMode.NONE - ) - ) - ) - val posts = listOf( - PostBuilder.of(text = "mute"), PostBuilder.of(text = "mutes"), PostBuilder.of(text = "hoge") - ) - val actual = muteProcessServiceImpl.processMutes( - posts, - FilterType.entries.toList(), - listOf( - filterQueryModel - ) - ) - - assertThat(actual) - .hasSize(2) - .containsEntry(posts[0], FilterResult(filterQueryModel, "mute")) - .containsEntry(posts[1], FilterResult(filterQueryModel, "mute")) - - } - - @Test - fun 何もマッチしないとnullが返ってくる() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "fuga", - FilterMode.NONE - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isNull() - } - - @Test - fun Cwで何もマッチしないと本文を確認する() = runTest { - val muteProcessServiceImpl = MuteProcessServiceImpl() - - val post = PostBuilder.of(overview = "hage", text = "mute test") - - val filterQueryModel = FilterQueryModel( - 1, - 2, - "mute test", - FilterType.entries, - FilterAction.warn, - listOf( - FilterKeyword( - 1, - 1, - "fuga", - FilterMode.NONE - ) - ) - ) - val actual = muteProcessServiceImpl.processMute( - post, FilterType.entries.toList(), listOf( - filterQueryModel - ) - ) - - assertThat(actual).isNull() - } - -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImplTest.kt deleted file mode 100644 index f37310a4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/filter/MuteServiceImplTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.* -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import dev.usbharu.hideout.core.query.model.FilterQueryService -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* - -@ExtendWith(MockitoExtension::class) -class MuteServiceImplTest { - - @Mock - private lateinit var filterRepository: FilterRepository - - @Mock - private lateinit var filterKeywordRepository: FilterKeywordRepository - - @Mock - private lateinit var filterQueryService: FilterQueryService - - @InjectMocks - private lateinit var muteServiceImpl: MuteServiceImpl - - @Test - fun createFilter() = runTest { - whenever(filterRepository.generateId()).doReturn(1) - whenever(filterKeywordRepository.generateId()).doReturn(1) - - whenever(filterRepository.save(any())).doAnswer { it.arguments[0]!! as Filter } - - val createFilter = muteServiceImpl.createFilter( - title = "hoge", - context = listOf(FilterType.home, FilterType.public), - action = FilterAction.warn, - keywords = listOf( - FilterKeyword( - "fuga", - FilterMode.NONE - ) - ), - loginUser = 1 - ) - - assertThat(createFilter).isEqualTo( - FilterQueryModel( - 1, - 1, - "hoge", - listOf(FilterType.home, FilterType.public), - FilterAction.warn, - keywords = listOf( - dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword(1, 1, "fuga", FilterMode.NONE) - ) - ) - ) - } - - @Test - fun getFilters() = runTest { - whenever(filterQueryService.findByUserIdAndType(any(), any())).doReturn( - listOf( - FilterQueryModel( - 1, - 1, - "hoge", - listOf(FilterType.home), - FilterAction.warn, - listOf( - dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword( - 1, - 1, - "fuga", - FilterMode.NONE - ) - ) - ) - ) - ) - - muteServiceImpl.getFilters(1, listOf(FilterType.home)) - } - - @Test - fun `getFilters 何も指定しない`() = runTest { - whenever(filterQueryService.findByUserIdAndType(any(), eq(emptyList()))).doReturn(emptyList()) - - muteServiceImpl.getFilters(1) - } -} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationServiceTest.kt deleted file mode 100644 index ee3aee68..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/ApatcheTikaFileTypeDeterminationServiceTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.service.media - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import kotlin.io.path.toPath - -class ApatcheTikaFileTypeDeterminationServiceTest { - @Test - fun png() { - val apatcheTikaFileTypeDeterminationService = ApatcheTikaFileTypeDeterminationService() - - val mimeType = apatcheTikaFileTypeDeterminationService.fileType( - String.javaClass.classLoader.getResource("400x400.png").toURI().toPath(), "400x400.png" - ) - - assertThat(mimeType.type).isEqualTo("image") - assertThat(mimeType.subtype).isEqualTo("png") - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt deleted file mode 100644 index 580c64c4..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.service.media - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.LocalStorageConfig -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import java.net.URL -import java.nio.file.Path -import java.util.* -import kotlin.io.path.readBytes -import kotlin.io.path.toPath - -class LocalFileSystemMediaDataStoreTest { - - private val path = String.javaClass.classLoader.getResource("400x400.png")?.toURI()?.toPath()!! - - @Test - fun `save inputStreamを使用して正常に保存できる`() = runTest { - val applicationConfig = ApplicationConfig(URL("https://example.com")) - val storageConfig = LocalStorageConfig("files", null) - - val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) - - val fileInputStream = path.readBytes() - - assertThat(fileInputStream.size).isNotEqualTo(0) - - val mediaSave = MediaSave( - "test-media-1${UUID.randomUUID()}.png", - "", - fileInputStream, - fileInputStream - ) - - val save = localFileSystemMediaDataStore.save(mediaSave) - - assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) - - save as SuccessSavedMedia - - assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - } - - @Test - fun 一時ファイルを使用して正常に保存できる() = runTest { - val applicationConfig = ApplicationConfig(URL("https://example.com")) - val storageConfig = LocalStorageConfig("files", null) - - val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) - - val fileInputStream = path.readBytes() - - assertThat(fileInputStream.size).isNotEqualTo(0) - - val saveRequest = MediaSaveRequest( - "test-media-2${UUID.randomUUID()}.png", - "", - path, - path - ) - - val save = localFileSystemMediaDataStore.save(saveRequest) - - assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) - - save as SuccessSavedMedia - - assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - } - - @Test - fun idを使用して削除できる() = runTest { - val applicationConfig = ApplicationConfig(URL("https://example.com")) - val storageConfig = LocalStorageConfig("files", null) - - val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) - - val fileInputStream = path.readBytes() - - assertThat(fileInputStream.size).isNotEqualTo(0) - - val saveRequest = MediaSaveRequest( - "test-media-2${UUID.randomUUID()}.png", - "", - path, - path - ) - - val save = localFileSystemMediaDataStore.save(saveRequest) - - assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) - - save as SuccessSavedMedia - - assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) - .exists() - .hasSize(fileInputStream.size.toLong()) - - - localFileSystemMediaDataStore.delete(save.name) - - assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) - .doesNotExist() - assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) - .doesNotExist() - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt deleted file mode 100644 index dcbcceb9..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import java.time.Instant - -class FollowNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = FollowNotificationRequest(1, 2).buildNotification(1, createdAt) - - Assertions.assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "follow", - userId = 1, - sourceActorId = 2, - postId = null, - text = null, - reactionId = null, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt deleted file mode 100644 index 0e577ffa..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/FollowRequestNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import java.time.Instant - -class FollowRequestNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = FollowRequestNotificationRequest(1, 2).buildNotification(1, createdAt) - - Assertions.assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "follow-request", - userId = 1, - sourceActorId = 2, - postId = null, - text = null, - reactionId = null, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt deleted file mode 100644 index 5bc72946..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/MentionNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import java.time.Instant - -class MentionNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = MentionNotificationRequest(1, 2, 3).buildNotification(1, createdAt) - - assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "mention", - userId = 1, - sourceActorId = 2, - postId = 3, - text = null, - reactionId = null, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt deleted file mode 100644 index aab94666..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository -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.relationship.RelationshipRepository -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.UserBuilder -import java.net.URL - -@ExtendWith(MockitoExtension::class) -class NotificationServiceImplTest { - - - @Mock - private lateinit var relationshipNotificationManagementService: RelationshipNotificationManagementService - - @Mock - private lateinit var relationshipRepository: RelationshipRepository - - @Spy - private val notificationStoreList: MutableList = mutableListOf() - - @Mock - private lateinit var notificationRepository: NotificationRepository - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var postRepository: PostRepository - - @Mock - private lateinit var reactionRepository: ReactionRepository - - @Spy - private val applicationConfig = ApplicationConfig(URL("https://example.com")) - - @InjectMocks - private lateinit var notificationServiceImpl: NotificationServiceImpl - - @Test - fun `publishNotifi ローカルユーザーへの通知を発行する`() = runTest { - - val actor = UserBuilder.localUserOf(domain = "example.com") - - whenever(actorRepository.findById(eq(1))).doReturn(actor) - - val id = TwitterSnowflakeIdGenerateService.generateId() - - whenever(notificationRepository.generateId()).doReturn(id) - - whenever(notificationRepository.save(any())).doAnswer { it.arguments[0] as Notification } - - - val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) - - assertThat(actual).isNotNull() - - verify(notificationRepository, times(1)).save(any()) - } - - @Test - fun `publishNotify ユーザーが存在しないときは発行しない`() = runTest { - val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) - - assertThat(actual).isNull() - } - - @Test - fun `publishNotify ユーザーがリモートユーザーの場合は発行しない`() = runTest { - val actor = UserBuilder.remoteUserOf(domain = "remote.example.com") - - whenever(actorRepository.findById(eq(1))).doReturn(actor) - - val actual = notificationServiceImpl.publishNotify(PostNotificationRequest(1, 2, 3)) - - assertThat(actual).isNull() - } - - @Test - fun unpublishNotify() = runTest { - notificationServiceImpl.unpublishNotify(1) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt deleted file mode 100644 index bc5bee91..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/PostNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import java.time.Instant - -class PostNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = PostNotificationRequest(1, 2, 3).buildNotification(1, createdAt) - - Assertions.assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "post", - userId = 1, - sourceActorId = 2, - postId = 3, - text = null, - reactionId = null, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt deleted file mode 100644 index 87483dcd..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/ReactionNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import java.time.Instant - -class ReactionNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = ReactionNotificationRequest(1, 2, 3, 4).buildNotification(1, createdAt) - - Assertions.assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "reaction", - userId = 1, - sourceActorId = 2, - postId = 3, - text = null, - reactionId = 4, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt deleted file mode 100644 index 43167eef..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RelationshipNotificationManagementServiceImplTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.relationship.Relationship -import org.junit.jupiter.api.Test -import kotlin.test.assertTrue - -class RelationshipNotificationManagementServiceImplTest { - @Test - fun `sendNotification ミューとしていない場合送信する`() { - val notification = RelationshipNotificationManagementServiceImpl().sendNotification( - Relationship( - 1, - 2, - false, - false, - false, - false, - false - ), PostNotificationRequest(1, 2, 3) - ) - - assertTrue(notification) - - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt deleted file mode 100644 index b50f9551..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/RepostNotificationRequestTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.service.notification - -import dev.usbharu.hideout.core.domain.model.notification.Notification -import org.assertj.core.api.Assertions -import org.junit.jupiter.api.Test -import java.time.Instant - -class RepostNotificationRequestTest { - - @Test - fun buildNotification() { - val createdAt = Instant.now() - val actual = RepostNotificationRequest(1, 2, 3).buildNotification(1, createdAt) - - Assertions.assertThat(actual).isEqualTo( - Notification( - id = 1, - type = "repost", - userId = 1, - sourceActorId = 2, - postId = 3, - text = null, - reactionId = null, - createdAt = createdAt - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatterTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatterTest.kt deleted file mode 100644 index 8725dd63..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/DefaultPostContentFormatterTest.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.service.post - -import dev.usbharu.hideout.application.config.HtmlSanitizeConfig -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -class DefaultPostContentFormatterTest { - val defaultPostContentFormatter = DefaultPostContentFormatter(HtmlSanitizeConfig().policy()) - - @Test - fun pタグがpタグになる() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun hタグがpタグになる() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun pタグのネストは破棄される() { - //language=HTML - val html = """

hoge

fuga

piyo

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

fuga

piyo

", "hoge\n\nfuga\n\npiyo")) - } - - @Test - fun spanタグは無視される() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun `2連続改行は段落に変換される`() { - //language=HTML - val html = """

hoge

fuga

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

fuga

", "hoge\n\nfuga")) - } - - @Test - fun iタグは無視される() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun aタグはhrefの中身のみ引き継がれる() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun aタグの中のspanは無視される() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun brタグのコンテンツは改行になる() { - //language=HTML - val html = """

hoge
fuga

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge
fuga

", "hoge\nfuga")) - } - - @Test - fun いきなりテキストが来たらpタグで囲む() { - //language=HTML - val html = """hoge""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun bodyタグが含まれていた場合消す() { - //language=HTML - val html = """

hoge

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo(FormattedPostContent("

hoge

", "hoge")) - } - - @Test - fun pタグの中のspanは無視される() { - //language=HTML - val html = - """

@testuser14 tes

""" - - val actual = defaultPostContentFormatter.format(html) - - assertThat(actual).isEqualTo( - FormattedPostContent( - "

@testuser14 tes

", - "@testuser14 tes" - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt deleted file mode 100644 index 80cbb571..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.service.post - -import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.application.config.HtmlSanitizeConfig -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.service.timeline.TimelineService -import jakarta.validation.Validation -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.mockStatic -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.PostBuilder -import utils.UserBuilder -import java.time.Instant - -@ExtendWith(MockitoExtension::class) -class PostServiceImplTest { - - @Mock - private lateinit var postRepository: PostRepository - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var timelineService: TimelineService - @Spy - private var postBuilder: Post.PostBuilder = Post.PostBuilder( - CharacterLimit(), DefaultPostContentFormatter( - HtmlSanitizeConfig().policy() - ), Validation.buildDefaultValidatorFactory().validator - ) - - @Mock - private lateinit var apSendCreateService: ApSendCreateService - - @Mock - private lateinit var reactionRepository: ReactionRepository - - @Mock - private lateinit var apSendDeleteService: APSendDeleteService - - @InjectMocks - private lateinit var postServiceImpl: PostServiceImpl - - @Test - fun `createLocal 正常にpostを作成できる`() = runTest { - - val now = Instant.now() - val post = PostBuilder.of(createdAt = now.toEpochMilli()) - - whenever(postRepository.save(eq(post))).doReturn(post) - whenever(postRepository.generateId()).doReturn(post.id) - whenever(actorRepository.findById(eq(post.actorId))).doReturn(UserBuilder.localUserOf(id = post.actorId)) - whenever(timelineService.publishTimeline(eq(post), eq(true))).doReturn(Unit) - - mockStatic(Instant::class.java, Mockito.CALLS_REAL_METHODS).use { - - it.`when`(Instant::now).doReturn(now) - val createLocal = postServiceImpl.createLocal( - PostCreateDto( - post.text, - post.overview, - post.visibility, - post.repostId, - post.replyId, - post.actorId, - post.mediaIds - ) - ) - - assertThat(createLocal).isEqualTo(post) - } - - verify(postRepository, times(1)).save(eq(post)) - verify(timelineService, times(1)).publishTimeline(eq(post), eq(true)) - verify(apSendCreateService, times(1)).createNote(eq(post)) - } - - @Test - fun `createRemote 正常にリモートのpostを作成できる`() = runTest { - val post = PostBuilder.of() - - whenever(actorRepository.findById(eq(post.actorId))).doReturn(UserBuilder.remoteUserOf(id = post.actorId)) - whenever(postRepository.save(eq(post))).doReturn(post) - whenever(timelineService.publishTimeline(eq(post), eq(false))).doReturn(Unit) - - - val createLocal = postServiceImpl.createRemote(post) - - assertThat(createLocal).isEqualTo(post) - - - verify(postRepository, times(1)).save(eq(post)) - verify(timelineService, times(1)).publishTimeline(eq(post), eq(false)) - } - - @Test - fun `createRemote 既に作成されていた場合はそのまま帰す`() = runTest { - val post = PostBuilder.of() - - whenever(actorRepository.findById(eq(post.actorId))).doReturn(UserBuilder.remoteUserOf(id = post.actorId)) - whenever(postRepository.save(eq(post))).doAnswer { throw DuplicateException() } - whenever(postRepository.findByApId(eq(post.apId))).doReturn(post) - - val createLocal = postServiceImpl.createRemote(post) - - assertThat(createLocal).isEqualTo(post) - - verify(postRepository, times(1)).save(eq(post)) - verify(timelineService, times(0)).publishTimeline(any(), any()) - } - - @Test - fun `createRemote 既に作成されていることを検知出来ずタイムラインにpush出来なかった場合何もしない`() = runTest { - val post = PostBuilder.of() - - whenever(actorRepository.findById(eq(post.actorId))).doReturn(UserBuilder.remoteUserOf(id = post.actorId)) - whenever(postRepository.save(eq(post))).doReturn(post) - whenever(timelineService.publishTimeline(eq(post), eq(false))).doThrow(DuplicateException::class) - whenever(postRepository.findByApId(eq(post.apId))).doReturn(post) - - val createLocal = postServiceImpl.createRemote(post) - - assertThat(createLocal).isEqualTo(post) - - verify(postRepository, times(1)).save(eq(post)) - verify(timelineService, times(1)).publishTimeline(eq(post), eq(false)) - } - - @Test - fun `deleteLocal Deleteが配送される`() = runTest { - val post = PostBuilder.of() - - val localUserOf = UserBuilder.localUserOf() - - whenever(actorRepository.findById(eq(post.actorId))).doReturn(localUserOf) - - postServiceImpl.deleteLocal(post) - - verify(reactionRepository, times(1)).deleteByPostId(eq(post.id)) - verify(postRepository, times(1)).save(eq(post.delete())) - verify(apSendDeleteService, times(1)).sendDeleteNote(eq(post)) - verify(actorRepository, times(1)).save(eq(localUserOf.decrementPostsCount())) - } - - @Test - fun `deleteLocal 削除済み投稿は何もしない`() = runTest { - val delete = PostBuilder.of().delete() - - postServiceImpl.deleteLocal(delete) - - verify(reactionRepository, never()).deleteByPostId(any()) - verify(postRepository, never()).save(any()) - verify(apSendDeleteService, never()).sendDeleteNote(any()) - verify(actorRepository, never()).save(any()) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt deleted file mode 100644 index e619f0f0..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.service.reaction - - -import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -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.ReactionRepository -import dev.usbharu.hideout.core.service.notification.NotificationService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.PostBuilder - -@ExtendWith(MockitoExtension::class) -class ReactionServiceImplTest { - - @Mock - private lateinit var notificationService: NotificationService - - @Mock - private lateinit var postRepository: PostRepository - - @Mock - private lateinit var reactionRepository: ReactionRepository - - @Mock - private lateinit var apReactionService: APReactionService - - @InjectMocks - private lateinit var reactionServiceImpl: ReactionServiceImpl - - @Test - fun `receiveReaction リアクションが存在しないとき保存する`() = runTest { - - val post = PostBuilder.of() - - whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( - false - ) - whenever(postRepository.findById(eq(post.id))).doReturn(post) - whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } - - val generateId = TwitterSnowflakeIdGenerateService.generateId() - whenever(reactionRepository.generateId()).doReturn(generateId) - - reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) - - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - } - - - @Test - fun `receiveReaction リアクションが既に作成されている場合削除して新しく作成`() = runTest { - val post = PostBuilder.of() - whenever(reactionRepository.existByPostIdAndActor(eq(post.id), eq(post.actorId))).doReturn( - true - ) - whenever(postRepository.findById(eq(post.id))).doReturn(post) - whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } - val generateId = TwitterSnowflakeIdGenerateService.generateId() - - whenever(reactionRepository.generateId()).doReturn(generateId) - - reactionServiceImpl.receiveReaction(UnicodeEmoji("❤"), post.actorId, post.id) - - verify(reactionRepository, times(1)).deleteByPostIdAndActorId(post.id, post.actorId) - verify(reactionRepository, times(1)).save(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId)) - } - - @Test - fun `sendReaction リアクションが存在しないとき保存して配送する`() = runTest { - val post = PostBuilder.of() - whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - null - ) - whenever(postRepository.findById(eq(post.id))).doReturn(post) - whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } - val generateId = TwitterSnowflakeIdGenerateService.generateId() - whenever(reactionRepository.generateId()).doReturn(generateId) - - reactionServiceImpl.sendReaction(UnicodeEmoji("❤"), post.actorId, post.id) - - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - } - - @Test - fun `sendReaction リアクションが存在するときは削除して保存して配送する`() = runTest { - val post = PostBuilder.of() - val id = TwitterSnowflakeIdGenerateService.generateId() - whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId) - ) - whenever(postRepository.findById(eq(post.id))).doReturn(post) - whenever(reactionRepository.save(any())).doAnswer { it.arguments[0] as Reaction } - val generateId = TwitterSnowflakeIdGenerateService.generateId() - whenever(reactionRepository.generateId()).doReturn(generateId) - - reactionServiceImpl.sendReaction(UnicodeEmoji("❤"), post.actorId, post.id) - - - verify(reactionRepository, times(1)).delete(eq(Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId))) - verify(reactionRepository, times(1)).save(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - verify(apReactionService, times(1)).removeReaction(eq(Reaction(id, UnicodeEmoji("❤"), post.id, post.actorId))) - verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, UnicodeEmoji("❤"), post.id, post.actorId))) - } - - @Test - fun `removeReaction リアクションが存在する場合削除して配送`() = runTest { - val post = PostBuilder.of() - whenever(reactionRepository.findByPostIdAndActorIdAndEmojiId(eq(post.id), eq(post.actorId), eq(0))).doReturn( - Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId) - ) - - reactionServiceImpl.removeReaction(post.actorId, post.id) - - verify(reactionRepository, times(1)).delete(eq(Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId))) - verify(apReactionService, times(1)).removeReaction(eq(Reaction(0, UnicodeEmoji("❤"), post.id, post.actorId))) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt deleted file mode 100644 index 4af69e07..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt +++ /dev/null @@ -1,798 +0,0 @@ -/* - * 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.service.relationship - -import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService -import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService -import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService -import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService -import dev.usbharu.hideout.application.config.ApplicationConfig -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.service.follow.SendFollowDto -import dev.usbharu.hideout.core.service.notification.NotificationService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.UserBuilder -import java.net.URL - -@ExtendWith(MockitoExtension::class) -class RelationshipServiceImplTest { - - - @Mock - private lateinit var notificationService: NotificationService - - @Spy - private val applicationConfig = ApplicationConfig(URL("https://example.com")) - - @Mock - private lateinit var relationshipRepository: RelationshipRepository - - @Mock - private lateinit var apSendFollowService: APSendFollowService - - @Mock - private lateinit var apSendAcceptService: ApSendAcceptService - - @Mock - private lateinit var apSendRejectService: ApSendRejectService - - @Mock - private lateinit var apSendUndoService: APSendUndoService - - @Mock - private lateinit var actorRepository: ActorRepository - - @InjectMocks - private lateinit var relationshipServiceImpl: RelationshipServiceImpl - - @Test - fun `followRequest ローカルの場合followRequestフラグがtrueで永続化される`() = runTest { - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `followRequest リモートの場合Followアクティビティが配送される`() = runTest { - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(localUser) - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(remoteUser) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendFollowService, times(1)).sendFollow(eq(SendFollowDto(localUser, remoteUser))) - } - - @Test - fun `followRequest ブロックされている場合フォローリクエスト出来ない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(null) - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `followRequest ブロックしている場合フォローリクエスト出来ない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `followRequest 既にフォローしている場合は念の為フォロー承認を自動で行う`() = runTest { - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(remoteUser) - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(localUser) - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendAcceptService, times(1)).sendAcceptFollow(eq(localUser), eq(remoteUser)) - verify(apSendFollowService, never()).sendFollow(any()) - } - - @Test - fun `followRequest フォローリクエスト無視の場合は無視する`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = true - ) - ) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.followRequest(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `block ローカルユーザーの場合永続化される`() = runTest { - whenever(actorRepository.findById(eq(1234))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - - relationshipServiceImpl.block(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `block リモートユーザーの場合永続化されて配送される`() = runTest { - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(localUser) - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(remoteUser) - - relationshipServiceImpl.block(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `acceptFollowRequest ローカルユーザーの場合永続化される`() = runTest { - whenever(actorRepository.findById(eq(1234))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - - verify(relationshipRepository, times(1)).save( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - verify(apSendAcceptService, never()).sendAcceptFollow(any(), any()) - } - - @Test - fun `acceptFollowRequest リモートユーザーの場合永続化されて配送される`() = runTest { - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(localUser) - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(remoteUser) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendAcceptService, times(1)).sendAcceptFollow(eq(localUser), eq(remoteUser)) - } - - @Test - fun `acceptFollowRequest Relationshipが存在しないときは何もしない`() = runTest { - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - - verify(apSendAcceptService, never()).sendAcceptFollow(any(), any()) - } - - @Test - fun `acceptFollowRequest フォローリクエストが存在せずforceがfalseのとき何もしない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - 5678, 1234, false, false, false, false, false - ) - ) - - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - - verify(apSendAcceptService, never()).sendAcceptFollow(any(), any()) - } - - @Test - fun `acceptFollowRequest フォローリクエストが存在せずforceがtrueのときフォローを承認する`() = runTest { - whenever(actorRepository.findById(eq(1234))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.remoteUserOf(domain = "remote.example.com")) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - 5678, 1234, false, false, false, false, false - ) - ) - - relationshipServiceImpl.acceptFollowRequest(1234, 5678, true) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `acceptFollowRequest ブロックしている場合は何もしない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - 5678, 1234, false, true, false, true, false - ) - ) - - assertThrows { - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - } - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `acceptFollowRequest ブロックされている場合は何もしない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - 1234, 5678, false, false, false, true, false - ) - ) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - 5678, 1234, false, true, false, true, false - ) - ) - - assertThrows { - relationshipServiceImpl.acceptFollowRequest(1234, 5678, false) - } - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `rejectFollowRequest ローカルユーザーの場合永続化される`() = runTest { - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.rejectFollowRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendRejectService, never()).sendRejectFollow(any(), any()) - } - - @Test - fun `rejectFollowRequest リモートユーザーの場合永続化されて配送される`() = runTest { - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(localUser) - - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(remoteUser) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.rejectFollowRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendRejectService, times(1)).sendRejectFollow(eq(localUser), eq(remoteUser)) - } - - @Test - fun `rejectFollowRequest Relationshipが存在しないとき何もしない`() = runTest { - - relationshipServiceImpl.rejectFollowRequest(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `rejectFollowRequest フォローリクエストが存在しない場合何もしない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.rejectFollowRequest(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `ignoreFollowRequest 永続化される`() = runTest { - relationshipServiceImpl.ignoreFollowRequest(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 5678, - targetActorId = 1234, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = true - ) - ) - ) - } - - @Test - fun `unfollow ローカルユーザーの場合永続化される`() = runTest { - whenever(actorRepository.findById(eq(1234))).doReturn(UserBuilder.remoteUserOf(domain = "remote.example.com")) - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com")) - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unfollow(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendUndoService, never()).sendUndoFollow(any(), any()) - } - - @Test - fun `unfollow リモートユーザー場合永続化されて配送される`() = runTest { - val localUser = UserBuilder.localUserOf(domain = "example.com") - whenever(actorRepository.findById(eq(1234))).doReturn(localUser) - - val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com") - whenever(actorRepository.findById(eq(5678))).doReturn(remoteUser) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unfollow(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - - verify(apSendUndoService, times(1)).sendUndoFollow(eq(localUser), eq(remoteUser)) - } - - @Test - fun `unfollow Relationshipが存在しないときは何もしない`() = runTest { - relationshipServiceImpl.unfollow(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `unfollow フォローしていなかった場合は何もしない`() = runTest { - whenever(actorRepository.findById(eq(1234))).doReturn(UserBuilder.localUserOf(id = 1234)) - whenever(actorRepository.findById(eq(5678))).doReturn(UserBuilder.localUserOf(id = 5678)) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unfollow(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `unblock ローカルユーザーの場合永続化される`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unblock(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `unblock リモートユーザーの場合永続化されて配送される`() = runTest { - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unblock(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - 1234, - 5678, - false, - false, - false, - false, - false - ) - ) - ) - } - - @Test - fun `unblock Relationshipがない場合何もしない`() = runTest { - relationshipServiceImpl.unblock(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `unblock ブロックしていない場合は何もしない`() = runTest { - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unblock(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } - - @Test - fun `mute ミュートが永続化される`() = runTest { - relationshipServiceImpl.mute(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = true, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `unmute 永続化される`() = runTest { - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = true, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - relationshipServiceImpl.unmute(1234, 5678) - - verify(relationshipRepository, times(1)).save( - eq( - Relationship( - actorId = 1234, - targetActorId = 5678, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - ) - } - - @Test - fun `unmute Relationshipが存在しない場合は何もしない`() = runTest { - relationshipServiceImpl.unmute(1234, 5678) - - verify(relationshipRepository, never()).save(any()) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveServiceTest.kt deleted file mode 100644 index a170e13e..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/resource/KtorResourceResolveServiceTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.service.resource - -import dev.usbharu.hideout.application.config.MediaConfig -import dev.usbharu.hideout.core.domain.exception.media.RemoteMediaFileSizeException -import io.ktor.client.* -import io.ktor.client.engine.mock.* -import io.ktor.http.* -import io.ktor.http.HttpHeaders.ContentLength -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension - -@ExtendWith(MockitoExtension::class) -class KtorResourceResolveServiceTest { - - @Spy - private val httpClient: HttpClient = HttpClient(MockEngine { - when (it.url.encodedPath) { - "/over-size-limit" -> { - respond(ByteArray(1000), HttpStatusCode.OK, Headers.build { - append(ContentLength, "1000") - }) - } - - else -> { - respond("Not Found", HttpStatusCode.NotFound) - } - } - }) { - expectSuccess = true - } - - @Spy - private val cacheManager: CacheManager = InMemoryCacheManager() - - @Spy - private val mediaConfig: MediaConfig = MediaConfig() - - @InjectMocks - private lateinit var ktorResourceResolveService: KtorResourceResolveService - - @Test - fun ファイルサイズ制限を超えたときRemoteMediaFileSizeExceptionが発生する() = runTest { - ktorResourceResolveService.sizeLimit = 100L - assertThrows { - ktorResourceResolveService.resolve("https://example.com/over-size-limit") - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt deleted file mode 100644 index e5806fc5..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.service.timeline - -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -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.post.Visibility -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import dev.usbharu.hideout.core.query.FollowerQueryService -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.ArgumentCaptor -import org.mockito.Captor -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.PostBuilder -import utils.UserBuilder - -@ExtendWith(MockitoExtension::class) -class TimelineServiceTest { - - @Mock - private lateinit var followerQueryService: FollowerQueryService - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var timelineRepository: TimelineRepository - - @InjectMocks - private lateinit var timelineService: TimelineService - - @Captor - private lateinit var captor: ArgumentCaptor> - - @Test - fun `publishTimeline ローカルの投稿はローカルのフォロワーと投稿者のタイムラインに追加される`() = runTest { - val post = PostBuilder.of() - val listOf = listOf(UserBuilder.localUserOf(), UserBuilder.localUserOf()) - val localUserOf = UserBuilder.localUserOf(id = post.actorId) - - whenever(followerQueryService.findFollowersById(eq(post.actorId))).doReturn(listOf) - whenever(actorRepository.findById(eq(post.actorId))).doReturn(localUserOf) - whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId()) - - - timelineService.publishTimeline(post, true) - - verify(timelineRepository).saveAll(capture(captor)) - val timelineList = captor.value - - assertThat(timelineList).hasSize(4).anyMatch { it.userId == post.actorId } - } - - @Test - fun `publishTimeline リモートの投稿はローカルのフォロワーのタイムラインに追加される`() = runTest { - val post = PostBuilder.of() - val listOf = listOf(UserBuilder.localUserOf(), UserBuilder.localUserOf()) - - whenever(followerQueryService.findFollowersById(eq(post.actorId))).doReturn(listOf) - whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId()) - - - timelineService.publishTimeline(post, false) - - verify(timelineRepository).saveAll(capture(captor)) - val timelineList = captor.value - - assertThat(timelineList).hasSize(3) - } - - @Test - fun `publishTimeline パブリック投稿はパブリックタイムラインにも追加される`() = runTest { - val post = PostBuilder.of() - val listOf = listOf(UserBuilder.localUserOf(), UserBuilder.localUserOf()) - - whenever(followerQueryService.findFollowersById(eq(post.actorId))).doReturn(listOf) - whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId()) - - - timelineService.publishTimeline(post, false) - - verify(timelineRepository).saveAll(capture(captor)) - val timelineList = captor.value - - assertThat(timelineList).hasSize(3).anyMatch { it.userId == 0L } - } - - @Test - fun `publishTimeline パブリック投稿ではない場合はローカルのフォロワーのみに追加される`() = runTest { - val post = PostBuilder.of(visibility = Visibility.UNLISTED) - val listOf = listOf(UserBuilder.localUserOf(), UserBuilder.localUserOf()) - - whenever(followerQueryService.findFollowersById(eq(post.actorId))).doReturn(listOf) - whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId()) - - - timelineService.publishTimeline(post, false) - - verify(timelineRepository).saveAll(capture(captor)) - val timelineList = captor.value - - assertThat(timelineList).hasSize(2).noneMatch { it.userId == 0L } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt deleted file mode 100644 index 6a574dc6..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package dev.usbharu.hideout.core.service.user - -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.CharacterLimit -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.deletedActor.DeletedActorRepository -import dev.usbharu.hideout.core.domain.model.instance.Instance -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.relationship.RelationshipRepository -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository -import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.owl.producer.api.OwlProducer -import jakarta.validation.Validation -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.ArgumentMatchers.anyString -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.TestApplicationConfig.testApplicationConfig -import java.net.URL -import java.security.KeyPairGenerator -import java.time.Instant -import kotlin.test.assertEquals -import kotlin.test.assertNull - -@ExtendWith(MockitoExtension::class) -class ActorServiceTest { - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var userAuthService: UserAuthService - - @Spy - private val actorBuilder = Actor.UserBuilder( - CharacterLimit(), - ApplicationConfig(URL("https://example.com")), - Validation.buildDefaultValidatorFactory().validator - ) - - @Spy - private val applicationConfig: ApplicationConfig = testApplicationConfig.copy(private = false) - - @Mock - private lateinit var instanceService: InstanceService - - @Mock - private lateinit var userDetailRepository: UserDetailRepository - - @Mock - private lateinit var deletedActorRepository: DeletedActorRepository - - @Mock - private lateinit var reactionRepository: ReactionRepository - - @Mock - private lateinit var relationshipRepository: RelationshipRepository - - @Mock - private lateinit var postService: PostService - - @Mock - private lateinit var apSendDeleteService: APSendDeleteService - - @Mock - private lateinit var postRepository: PostRepository - - @Mock - private lateinit var owlProducer: OwlProducer - - @InjectMocks - private lateinit var userService: UserServiceImpl - - @Test - fun `createLocalUser ローカルユーザーを作成できる`() = runTest { - - val generateKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair() - whenever(actorRepository.nextId()).doReturn(110001L) - whenever(userAuthService.hash(anyString())).doReturn("hashedPassword") - whenever(userAuthService.generateKeyPair()).doReturn(generateKeyPair) - - - - userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test")) - verify(actorRepository, times(1)).save(any()) - argumentCaptor { - verify(actorRepository, times(1)).save(capture()) - assertEquals("test", firstValue.name) - assertEquals("testUser", firstValue.screenName) - assertEquals("XXXXXXXXXXXXX", firstValue.description) - assertEquals(110001L, firstValue.id) - assertEquals("https://example.com/users/test", firstValue.url) - assertEquals("example.com", firstValue.domain) - assertEquals("https://example.com/users/test/inbox", firstValue.inbox) - assertEquals("https://example.com/users/test/outbox", firstValue.outbox) - assertEquals(generateKeyPair.public.toPem(), firstValue.publicKey) - assertEquals(generateKeyPair.private.toPem(), firstValue.privateKey) - } - } - - @Test - fun `createLocalUser applicationconfig privateがtrueのときアカウントを作成できない`() = runTest { - whenever(applicationConfig.private).thenReturn(true) - - assertThrows { - userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test")) - } - - } - - @Test - fun `createRemoteUser リモートユーザーを作成できる`() = runTest { - - whenever(actorRepository.nextId()).doReturn(113345L) - whenever(instanceService.fetchInstance(eq("https://remote.example.com"), isNull())).doReturn( - Instance( - 12345L, - "", - "", - "https://remote.example.com", - "https://remote.example.com/favicon.ico", - null, - "unknown", - "", - false, - false, - "", - Instant.now() - ) - ) - - val user = RemoteUserCreateDto( - name = "test", - domain = "remote.example.com", - screenName = "testUser", - description = "test user", - inbox = "https://remote.example.com/inbox", - outbox = "https://remote.example.com/outbox", - url = "https://remote.example.com", - publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", - keyId = "a", - following = "", - followers = "", - sharedInbox = null, - locked = false - ) - userService.createRemoteUser(user) - verify(actorRepository, times(1)).save(any()) - argumentCaptor { - verify(actorRepository, times(1)).save(capture()) - assertEquals("test", firstValue.name) - assertEquals("testUser", firstValue.screenName) - assertEquals("test user", firstValue.description) - assertEquals(113345L, firstValue.id) - assertEquals("https://remote.example.com", firstValue.url) - assertEquals("remote.example.com", firstValue.domain) - assertEquals("https://remote.example.com/inbox", firstValue.inbox) - assertEquals("https://remote.example.com/outbox", firstValue.outbox) - assertEquals("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", firstValue.publicKey) - assertNull(firstValue.privateKey) - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt deleted file mode 100644 index 1dd7e5b2..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationTypeTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.mastodon.domain.model - -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import org.junit.jupiter.params.provider.ValueSource -import java.util.stream.Stream -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class NotificationTypeTest { - @ParameterizedTest - @MethodSource("parseSuccessProvider") - fun parseに成功する(s: String, notificationType: NotificationType) { - assertEquals(notificationType, NotificationType.parse(s)) - } - - @ParameterizedTest - @ValueSource(strings = ["hoge", "fuga", "0x1234", "follow_reject", "test", "mentiooon", "emoji_reaction", "reaction"]) - fun parseに失敗する(s: String) { - assertNull(NotificationType.parse(s)) - } - - companion object { - @JvmStatic - fun parseSuccessProvider(): Stream { - return Stream.of( - arguments("mention", NotificationType.mention), - arguments("status", NotificationType.status), - arguments("reblog", NotificationType.reblog), - arguments("follow", NotificationType.follow), - arguments("follow_request", NotificationType.follow_request), - arguments("favourite", NotificationType.favourite), - arguments("poll", NotificationType.poll), - arguments("update", NotificationType.update), - arguments("admin.sign_up", NotificationType.admin_sign_up), - arguments("admin.report", NotificationType.admin_report), - arguments("servered_relationships", NotificationType.severed_relationships) - ) - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt deleted file mode 100644 index 9ecb49ff..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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.mastodon.interfaces.api.account - -import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.infrastructure.springframework.security.OAuth2JwtLoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.AccountSource -import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccount -import dev.usbharu.hideout.domain.mastodon.model.generated.Role -import dev.usbharu.hideout.mastodon.service.account.AccountApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import org.springframework.http.MediaType -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import utils.TestTransaction -import java.net.URL - -@ExtendWith(MockitoExtension::class) -class MastodonAccountApiControllerTest { - - private lateinit var mockMvc: MockMvc - - @Spy - private val loginUserContextHolder = OAuth2JwtLoginUserContextHolder() - - @Spy - private lateinit var testTransaction: TestTransaction - - @Mock - private lateinit var accountApiService: AccountApiService - - @Spy - private val applicationConfig: ApplicationConfig = ApplicationConfig(URL("https://example.com")) - - @InjectMocks - private lateinit var mastodonAccountApiController: MastodonAccountApiController - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonAccountApiController).build() - } - - @Test - fun `apiV1AccountsVerifyCredentialsGet JWTで認証時に200が返ってくる`() = runTest { - - val createEmptyContext = SecurityContextHolder.createEmptyContext() - createEmptyContext.authentication = JwtAuthenticationToken( - Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build() - ) - SecurityContextHolder.setContext(createEmptyContext) - val credentialAccount = CredentialAccount( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - source = AccountSource( - note = "", - fields = emptyList(), - privacy = AccountSource.Privacy.public, - sensitive = false, - followRequestsCount = 0 - ), - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0, - role = Role(0, "ADMIN", "", 0, false) - ) - whenever(accountApiService.verifyCredentials(eq(1234))).doReturn(credentialAccount) - - val objectMapper = ActivityPubConfig().objectMapper() - - mockMvc - .get("/api/v1/accounts/verify_credentials") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(credentialAccount)) } } - } - - @Test - fun `apiV1AccountsVerifyCredentialsGet POSTは405が返ってくる`() { - mockMvc.post("/api/v1/accounts/verify_credentials") - .andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `apiV1AccountsPost GETは405が返ってくる`() { - mockMvc.get("/api/v1/accounts") - .andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `apiV1AccountsPost アカウント作成成功時302とアカウントのurlが返ってくる`() { - mockMvc - .post("/api/v1/accounts") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("username", "hoge") - param("password", "very_secure_password") - param("email", "email@example.com") - param("agreement", "true") - param("locale", "true") - }.asyncDispatch() - .andExpect { header { string("location", "/users/hoge") } } - .andExpect { status { isFound() } } - } - - @Test - fun `apiV1AccountsIdFollowPost フォロー成功時は200が返ってくる`() { - val createEmptyContext = SecurityContextHolder.createEmptyContext() - createEmptyContext.authentication = JwtAuthenticationToken( - Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build() - ) - SecurityContextHolder.setContext(createEmptyContext) - mockMvc - .post("/api/v1/accounts/1/follow") { - contentType = MediaType.APPLICATION_JSON - } - .asyncDispatch() - .andExpect { status { isOk() } } - - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiControllerTest.kt deleted file mode 100644 index fca0dce0..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiControllerTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.mastodon.interfaces.api.apps - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.domain.mastodon.model.generated.Application -import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest -import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor -import dev.usbharu.hideout.mastodon.service.app.AppApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import org.springframework.http.MediaType -import org.springframework.http.converter.HttpMessageConverter -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.method.annotation.ModelAttributeMethodProcessor -import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - -@ExtendWith(MockitoExtension::class) -class MastodonAppsApiControllerTest { - - @Mock - private lateinit var appApiService: AppApiService - - @InjectMocks - private lateinit var mastodonAppsApiController: MastodonAppsApiController - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonAppsApiController).setCustomArgumentResolvers( - JsonOrFormModelMethodProcessor( - ModelAttributeMethodProcessor(false), RequestResponseBodyMethodProcessor( - mutableListOf>( - MappingJackson2HttpMessageConverter() - ) - ) - ) - ).build() - } - - @Test - fun `apiV1AppsPost JSONで作成に成功したら200が返ってくる`() = runTest { - - val appsRequest = AppsRequest( - "test", - "https://example.com", - "write", - null - ) - val application = Application( - "test", - "", - null, - "safdash;", - "aksdhgoa" - ) - - whenever(appApiService.createApp(eq(appsRequest))).doReturn(application) - - val objectMapper = jacksonObjectMapper() - val writeValueAsString = objectMapper.writeValueAsString(appsRequest) - - mockMvc - .post("/api/v1/apps") { - contentType = MediaType.APPLICATION_JSON - content = writeValueAsString - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(application)) } } - } - - @Test - fun `apiV1AppsPost FORMで作成に成功したら200が返ってくる`() = runTest { - - val appsRequest = AppsRequest( - "test", - "https://example.com", - "write", - null - ) - val application = Application( - "test", - "", - null, - "safdash;", - "aksdhgoa" - ) - - whenever(appApiService.createApp(eq(appsRequest))).doReturn(application) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .post("/api/v1/apps") { - contentType = MediaType.APPLICATION_FORM_URLENCODED - param("client_name", "test") - param("redirect_uris", "https://example.com") - param("scopes", "write") - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(application)) } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiControllerTest.kt deleted file mode 100644 index bca820da..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiControllerTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.mastodon.interfaces.api.instance - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.service.instance.InstanceApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders - -@ExtendWith(MockitoExtension::class) -class MastodonInstanceApiControllerTest { - - @Mock - private lateinit var instanceApiService: InstanceApiService - - @InjectMocks - private lateinit var mastodonInstanceApiController: MastodonInstanceApiController - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonInstanceApiController).build() - } - - @Test - fun `apiV1InstanceGet GETしたら200が返ってくる`() = runTest { - - val v1Instance = V1Instance( - uri = "https://example.com", - title = "hideout", - shortDescription = "test", - description = "test instance", - email = "test@example.com", - version = "0.0.1", - urls = V1InstanceUrls(streamingApi = "https://example.com/atreaming"), - stats = V1InstanceStats(userCount = 1, statusCount = 0, domainCount = 0), - thumbnail = "https://example.com", - languages = emptyList(), - registrations = false, - approvalRequired = false, - invitesEnabled = false, - configuration = V1InstanceConfiguration( - accounts = V1InstanceConfigurationAccounts(0), - V1InstanceConfigurationStatuses(100, 4, 23), - V1InstanceConfigurationMediaAttachments(emptyList(), 100, 100, 100, 100, 100), - V1InstanceConfigurationPolls( - 10, 10, 10, 10 - ) - ), - contactAccount = Account( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - emptyList() - ) - whenever(instanceApiService.v1Instance()).doReturn(v1Instance) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .get("/api/v1/instance") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(objectMapper)) } } - } - - @Test - fun `apiV1InstanceGet POSTしたら405が返ってくる`() { - mockMvc - .post("/api/v1/instance") - .andExpect { status { isMethodNotAllowed() } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiControllerTest.kt deleted file mode 100644 index 24e79604..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiControllerTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.mastodon.interfaces.api.media - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.service.media.MediaApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import org.springframework.mock.web.MockMultipartFile -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.multipart -import org.springframework.test.web.servlet.setup.MockMvcBuilders - -@ExtendWith(MockitoExtension::class) -class MastodonMediaApiControllerTest { - - @Mock - private lateinit var mediaApiService: MediaApiService - - @InjectMocks - private lateinit var mastodonMediaApiController: MastodonMediaApiController - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonMediaApiController).build() - } - - @Test - fun `apiV1MediaPost ファイルとサムネイルをアップロードできる`() = runTest { - - val mediaAttachment = MediaAttachment( - id = "1234", - type = MediaAttachment.Type.image, - url = "https://example.com", - previewUrl = "https://example.com", - remoteUrl = "https://example.com", - description = "pngImageStream", - blurhash = "", - textUrl = "https://example.com" - ) - whenever(mediaApiService.postMedia(any())).doReturn(mediaAttachment) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .multipart("/api/v1/media") { - file(MockMultipartFile("file", "test.png", "image/png", "jpgImageStream".toByteArray())) - file(MockMultipartFile("thumbnail", "thumbnail.png", "image/png", "pngImageStream".toByteArray())) - param("description", "jpgImage") - param("focus", "") - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(mediaAttachment)) } } - } - - @Test - fun `apiV1MediaPost ファイルだけをアップロードできる`() = runTest { - - val mediaAttachment = MediaAttachment( - id = "1234", - type = MediaAttachment.Type.image, - url = "https://example.com", - previewUrl = "https://example.com", - remoteUrl = "https://example.com", - description = "pngImageStream", - blurhash = "", - textUrl = "https://example.com" - ) - whenever(mediaApiService.postMedia(any())).doReturn(mediaAttachment) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .multipart("/api/v1/media") { - file(MockMultipartFile("file", "test.png", "image/png", "jpgImageStream".toByteArray())) - param("description", "jpgImage") - param("focus", "") - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(mediaAttachment)) } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiControllerTest.kt deleted file mode 100644 index ff2047ea..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiControllerTest.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.mastodon.interfaces.api.status - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.core.infrastructure.springframework.security.OAuth2JwtLoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor -import dev.usbharu.hideout.mastodon.service.status.StatusesApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import org.springframework.http.MediaType -import org.springframework.http.converter.HttpMessageConverter -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.method.annotation.ModelAttributeMethodProcessor -import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - -@ExtendWith(MockitoExtension::class) -class MastodonStatusesApiControllerTest { - - @Spy - private val loginUserContextHolder = OAuth2JwtLoginUserContextHolder() - - @Mock - private lateinit var statusesApiService: StatusesApiService - - @InjectMocks - private lateinit var mastodonStatusesApiController: MastodonStatusesApiContoller - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonStatusesApiController).setCustomArgumentResolvers( - JsonOrFormModelMethodProcessor( - ModelAttributeMethodProcessor(false), RequestResponseBodyMethodProcessor( - mutableListOf>( - MappingJackson2HttpMessageConverter() - ) - ) - ) - ).build() - } - - @Test - fun `apiV1StatusesPost JWT認証時POSTすると投稿できる`() = runTest { - val createEmptyContext = SecurityContextHolder.createEmptyContext() - createEmptyContext.authentication = JwtAuthenticationToken( - Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build() - ) - SecurityContextHolder.setContext(createEmptyContext) - val status = Status( - id = "", - uri = "", - createdAt = "", - account = Account( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - content = "", - visibility = Status.Visibility.public, - sensitive = false, - spoilerText = "", - mediaAttachments = emptyList(), - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = "https://example.com", - inReplyToId = null, - inReplyToAccountId = null, - language = "ja_JP", - text = "Test", - editedAt = null - - ) - - val objectMapper = jacksonObjectMapper() - - val statusesRequest = StatusesRequest() - - statusesRequest.status = "hello" - - whenever(statusesApiService.postStatus(eq(statusesRequest), eq(1234))).doReturn(status) - - mockMvc - .post("/api/v1/statuses") { - contentType = MediaType.APPLICATION_JSON - content = objectMapper.writeValueAsString(statusesRequest) - } - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(status)) } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiControllerTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiControllerTest.kt deleted file mode 100644 index 8302d0cf..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiControllerTest.kt +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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.mastodon.interfaces.api.timeline - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.infrastructure.springframework.security.OAuth2JwtLoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.service.timeline.TimelineApiService -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import org.springframework.test.web.servlet.post -import org.springframework.test.web.servlet.setup.MockMvcBuilders -import java.net.URL - -@ExtendWith(MockitoExtension::class) -class MastodonTimelineApiControllerTest { - - @Spy - private val loginUserContextHolder = OAuth2JwtLoginUserContextHolder() - - @Mock - private lateinit var timelineApiService: TimelineApiService - - @Spy - private val applicationConfig: ApplicationConfig = ApplicationConfig(URL("https://example.com")) - - @InjectMocks - private lateinit var mastodonTimelineApiController: MastodonTimelineApiController - - private lateinit var mockMvc: MockMvc - - @BeforeEach - fun setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(mastodonTimelineApiController).build() - } - - val statusList = PaginationList( - listOf( - Status( - id = "", - uri = "", - createdAt = "", - account = Account( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - content = "", - visibility = Status.Visibility.public, - sensitive = false, - spoilerText = "", - mediaAttachments = emptyList(), - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = "https://example.com", - inReplyToId = null, - inReplyToAccountId = null, - language = "ja_JP", - text = "Test", - editedAt = null - - ), - Status( - id = "", - uri = "", - createdAt = "", - account = Account( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - content = "", - visibility = Status.Visibility.public, - sensitive = false, - spoilerText = "", - mediaAttachments = emptyList(), - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = "https://example.com", - inReplyToId = null, - inReplyToAccountId = null, - language = "ja_JP", - text = "Test", - editedAt = null - - ) - ), null, null - ) - - @Test - fun `apiV1TimelineHogeGet JWT認証でログインじ200が返ってくる`() = runTest { - - val createEmptyContext = SecurityContextHolder.createEmptyContext() - createEmptyContext.authentication = JwtAuthenticationToken( - Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build() - ) - SecurityContextHolder.setContext(createEmptyContext) - - whenever( - timelineApiService.homeTimeline( - eq(1234), - any() - ) - ).doReturn(statusList) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .get("/api/v1/timelines/home?max_id=123456&since_id=1234567&min_id=54321&limit=20") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(statusList)) } } - } - - @Test - fun `apiV1TimelineHomeGet パラメーターがなくても取得できる`() = runTest { - val createEmptyContext = SecurityContextHolder.createEmptyContext() - createEmptyContext.authentication = JwtAuthenticationToken( - Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build() - ) - SecurityContextHolder.setContext(createEmptyContext) - - whenever( - timelineApiService.homeTimeline( - eq(1234), - any() - ) - ).doReturn(statusList) - - val objectMapper = jacksonObjectMapper() - - mockMvc - .get("/api/v1/timelines/home") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(statusList)) } } - } - - @Test - fun `apiV1TimelineHomeGet POSTには405を返す`() { - mockMvc - .post("/api/v1/timelines/home?max_id=123456&since_id=1234567&min_id=54321&limit=20") - .andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `apiV1TimelinePublicGet GETで200が返ってくる`() = runTest { - whenever( - timelineApiService.publicTimeline( - localOnly = eq(false), - remoteOnly = eq(true), - mediaOnly = eq(false), - any() - ) - ).doAnswer { - println(it.arguments.joinToString()) - statusList - } - - val objectMapper = jacksonObjectMapper() - - mockMvc - .get("/api/v1/timelines/public?local=false&remote=true&only_media=false&max_id=1234&since_id=12345&min_id=4321&limit=20") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(statusList)) } } - } - - @Test - fun `apiV1TimelinePublicGet POSTで405が返ってくる`() { - mockMvc.post("/api/v1/timelines/public") - .andExpect { status { isMethodNotAllowed() } } - } - - @Test - fun `apiV1TimelinePublicGet パラメーターがなくても取得できる`() = runTest { - whenever( - timelineApiService.publicTimeline( - localOnly = eq(false), - remoteOnly = eq(false), - mediaOnly = eq(false), - any() - ) - ).doAnswer { - println(it.arguments.joinToString()) - statusList - } - - val objectMapper = jacksonObjectMapper() - - mockMvc - .get("/api/v1/timelines/public") - .asyncDispatch() - .andExpect { status { isOk() } } - .andExpect { content { json(objectMapper.writeValueAsString(statusList)) } } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt deleted file mode 100644 index de1477c6..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt +++ /dev/null @@ -1,327 +0,0 @@ -/* - * 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.mastodon.service.account - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.hideout.core.service.user.UserService -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.domain.mastodon.model.generated.Relationship -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import kotlinx.coroutines.test.runTest -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.* -import utils.TestTransaction - -@ExtendWith(MockitoExtension::class) -class AccountApiServiceImplTest { - - @Mock - private lateinit var accountService: AccountService - - @Mock - private lateinit var userService: UserService - - @Mock - private lateinit var actorRepository: ActorRepository - - @Mock - private lateinit var followerQueryService: FollowerQueryService - - @Mock - private lateinit var statusQueryService: StatusQueryService - - @Spy - private val transaction: Transaction = TestTransaction - - @Mock - private lateinit var relationshipService: RelationshipService - - @Mock - private lateinit var relationshipRepository: RelationshipRepository - - @Mock - private lateinit var mediaService: MediaService - - @InjectMocks - private lateinit var accountApiServiceImpl: AccountApiServiceImpl - - private val statusList = PaginationList( - listOf( - Status( - id = "", - uri = "", - createdAt = "", - account = Account( - id = "", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = "", - lastStatusAt = "", - statusesCount = 0, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - content = "", - visibility = Status.Visibility.public, - sensitive = false, - spoilerText = "", - mediaAttachments = emptyList(), - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = "https://example.com", - inReplyToId = null, - inReplyToAccountId = null, - language = "ja_JP", - text = "Test", - editedAt = null - ) - ), null, null - ) - - @Test - fun `accountsStatuses 非ログイン時は非公開投稿を見れない`() = runTest { - val userId = 1234L - - whenever( - statusQueryService.accountsStatus( - accountId = eq(userId), - onlyMedia = eq(false), - excludeReplies = eq(false), - excludeReblogs = eq(false), - pinned = eq(false), - tagged = isNull(), - includeFollowers = eq(false), - page = any() - ) - ).doReturn( - statusList - ) - - - val accountsStatuses = accountApiServiceImpl.accountsStatuses( - userid = userId, - onlyMedia = false, - excludeReplies = false, - excludeReblogs = false, - pinned = false, - tagged = null, - loginUser = null, - Page.of() - ) - - assertThat(accountsStatuses).hasSize(1) - - verify(followerQueryService, never()).alreadyFollow(any(), any()) - } - - @Test - fun `accountsStatuses ログイン時フォロワーじゃない場合は非公開投稿を見れない`() = runTest { - val userId = 1234L - val loginUser = 1L - whenever( - statusQueryService.accountsStatus( - accountId = eq(userId), - onlyMedia = eq(false), - excludeReplies = eq(false), - excludeReblogs = eq(false), - pinned = eq(false), - tagged = isNull(), - includeFollowers = eq(false), - page = any() - ) - ).doReturn(statusList) - - val accountsStatuses = accountApiServiceImpl.accountsStatuses( - userid = userId, - onlyMedia = false, - excludeReplies = false, - excludeReblogs = false, - pinned = false, - tagged = null, - loginUser = loginUser, - Page.of() - ) - - assertThat(accountsStatuses).hasSize(1) - } - - @Test - fun `accountsStatuses ログイン時フォロワーの場合は非公開投稿を見れる`() = runTest { - val userId = 1234L - val loginUser = 2L - whenever( - statusQueryService.accountsStatus( - accountId = eq(userId), - onlyMedia = eq(false), - excludeReplies = eq(false), - excludeReblogs = eq(false), - pinned = eq(false), - tagged = isNull(), - includeFollowers = eq(true), - page = any() - ) - ).doReturn(statusList) - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(loginUser), eq(userId))).doReturn( - dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = loginUser, - targetActorId = userId, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - - val accountsStatuses = accountApiServiceImpl.accountsStatuses( - userid = userId, - onlyMedia = false, - excludeReplies = false, - excludeReblogs = false, - pinned = false, - tagged = null, - loginUser = loginUser, - Page.of() - ) - - assertThat(accountsStatuses).hasSize(1) - } - - @Test - fun `follow 未フォローの場合フォローリクエストが発生する`() = runTest { - val userId = 1234L - val followeeId = 1L - - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(followeeId), eq(userId))).doReturn( - dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = followeeId, - targetActorId = userId, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(userId), eq(followeeId))).doReturn( - dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = userId, - targetActorId = followeeId, - following = true, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - ) - - - val follow = accountApiServiceImpl.follow(userId, followeeId) - - val expected = Relationship( - id = followeeId.toString(), - following = true, - showingReblogs = true, - notifying = false, - followedBy = true, - blocking = false, - blockedBy = false, - muting = false, - mutingNotifications = false, - requested = false, - domainBlocking = false, - endorsed = false, - note = "" - ) - assertThat(follow).isEqualTo(expected) - - verify(relationshipService, times(1)).followRequest(eq(userId), eq(followeeId)) - } - - @Test - fun `relationships idが長すぎたら省略する`() = runTest { - - val relationships = accountApiServiceImpl.relationships( - userid = 1234L, - id = (1..30L).toList(), - withSuspended = false - ) - - assertThat(relationships).hasSize(20) - } - - @Test - fun `relationships id0の場合即時return`() = runTest { - val relationships = accountApiServiceImpl.relationships( - userid = 1234L, - id = emptyList(), - withSuspended = false - ) - - assertThat(relationships).hasSize(0) - verify(followerQueryService, never()).alreadyFollow(any(), any()) - } - - @Test - fun `relationships idに指定されたアカウントの関係を取得する`() = runTest { - - val relationships = accountApiServiceImpl.relationships( - userid = 1234L, - id = (1..15L).toList(), - withSuspended = false - ) - - assertThat(relationships).hasSize(15) - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt deleted file mode 100644 index 937f7e8b..00000000 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/util/EmojiUtilTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.util - -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource - -class EmojiUtilTest { - - @Test - fun 絵文字を判定できる() { - val emoji = "❤" - val actual = EmojiUtil.isEmoji(emoji) - - assertThat(actual).isTrue() - } - - @Test - fun ただの文字を判定できる() { - val moji = "blobblinkhyper" - val actual = EmojiUtil.isEmoji(moji) - - assertThat(actual).isFalse() - } - - @ParameterizedTest - @ValueSource(strings = ["❤", "🌄", "🤗", "⛺", "🧑‍🤝‍🧑", "🖐🏿"]) - fun `絵文字判定`(s: String) { - val actual = EmojiUtil.isEmoji(s) - - assertThat(actual).isTrue() - } - - @ParameterizedTest - @ValueSource(strings = ["™", "㍂", "㌠"]) - fun `文字判定`(s: String) { - val actual = EmojiUtil.isEmoji(s) - - assertThat(actual).isFalse() - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/InstantParseUtil.kt b/hideout-core/src/test/kotlin/utils/AssertDomainEvent.kt similarity index 50% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/util/InstantParseUtil.kt rename to hideout-core/src/test/kotlin/utils/AssertDomainEvent.kt index fd6c3669..d1d2c049 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/InstantParseUtil.kt +++ b/hideout-core/src/test/kotlin/utils/AssertDomainEvent.kt @@ -14,21 +14,22 @@ * limitations under the License. */ -package dev.usbharu.hideout.util +package utils -import java.time.Instant -import java.time.format.DateTimeParseException +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable -object InstantParseUtil { - fun parse(str: String?): Instant? { - return try { - Instant.ofEpochMilli(str?.toLong() ?: return null) - } catch (e: NumberFormatException) { - try { - Instant.parse(str) - } catch (e: DateTimeParseException) { - null - } +object AssertDomainEvent { + fun assertContainsEvent(domainEventStorable: DomainEventStorable, eventName: String) { + val find = domainEventStorable.getDomainEvents().find { it.name == eventName } + + if (find == null) { + throw AssertionError("Domain Event not found: $eventName") } } -} + + fun assertEmpty(domainEventStorable: DomainEventStorable) { + if (domainEventStorable.getDomainEvents().isNotEmpty()) { + throw AssertionError("Domain Event found") + } + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/utils/JsonObjectMapper.kt b/hideout-core/src/test/kotlin/utils/JsonObjectMapper.kt deleted file mode 100644 index 41cb81c3..00000000 --- a/hideout-core/src/test/kotlin/utils/JsonObjectMapper.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 utils - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper - -object JsonObjectMapper { - val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = - jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - - init { - objectMapper.configOverride(List::class.java).setSetterInfo( - JsonSetter.Value.forValueNulls( - Nulls.AS_EMPTY - ) - ) - } -} diff --git a/hideout-core/src/test/kotlin/utils/PostBuilder.kt b/hideout-core/src/test/kotlin/utils/PostBuilder.kt deleted file mode 100644 index 4ddd2e89..00000000 --- a/hideout-core/src/test/kotlin/utils/PostBuilder.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 utils - -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.application.config.HtmlSanitizeConfig -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter -import jakarta.validation.Validation -import kotlinx.coroutines.runBlocking -import java.time.Instant - -object PostBuilder { - - private val postBuilder = - Post.PostBuilder( - CharacterLimit(), - DefaultPostContentFormatter(HtmlSanitizeConfig().policy()), - Validation.buildDefaultValidatorFactory().validator - ) - - private val idGenerator = TwitterSnowflakeIdGenerateService - - fun of( - id: Long = generateId(), - userId: Long = generateId(), - overview: String? = null, - text: String = "Hello World", - createdAt: Long = Instant.now().toEpochMilli(), - visibility: Visibility = Visibility.PUBLIC, - url: String = "https://example.com/users/$userId/posts/$id", - ): Post { - return postBuilder.of( - id = id, - actorId = userId, - overview = overview, - content = text, - createdAt = createdAt, - visibility = visibility, - url = url, - ) - } - - private fun generateId(): Long = runBlocking { - idGenerator.generateId() - } -} diff --git a/hideout-core/src/test/kotlin/utils/TestApplicationConfig.kt b/hideout-core/src/test/kotlin/utils/TestApplicationConfig.kt deleted file mode 100644 index 699e8777..00000000 --- a/hideout-core/src/test/kotlin/utils/TestApplicationConfig.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 utils - -import dev.usbharu.hideout.application.config.ApplicationConfig -import java.net.URL - -object TestApplicationConfig { - val testApplicationConfig = ApplicationConfig(URL("https://example.com")) -} diff --git a/hideout-core/src/test/kotlin/utils/TestTransaction.kt b/hideout-core/src/test/kotlin/utils/TestTransaction.kt index 4fc832fd..a32bf303 100644 --- a/hideout-core/src/test/kotlin/utils/TestTransaction.kt +++ b/hideout-core/src/test/kotlin/utils/TestTransaction.kt @@ -16,7 +16,7 @@ package utils -import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.application.shared.Transaction object TestTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T = block() diff --git a/hideout-core/src/test/kotlin/utils/UserBuilder.kt b/hideout-core/src/test/kotlin/utils/UserBuilder.kt deleted file mode 100644 index 0aff8e44..00000000 --- a/hideout-core/src/test/kotlin/utils/UserBuilder.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 utils - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import jakarta.validation.Validation -import kotlinx.coroutines.runBlocking -import java.net.URL -import java.time.Instant - -object UserBuilder { - private val actorBuilder = Actor.UserBuilder( - CharacterLimit(), ApplicationConfig(URL("https://example.com")), - Validation.buildDefaultValidatorFactory().validator - ) - - private val idGenerator = TwitterSnowflakeIdGenerateService - - fun localUserOf( - id: Long = generateId(), - name: String = "test-user-$id", - domain: String = "example.com", - screenName: String = name, - description: String = "This user is test user.", - inbox: String = "https://$domain/users/$id/inbox", - outbox: String = "https://$domain/users/$id/outbox", - url: String = "https://$domain/users/$id", - publicKey: String = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", - privateKey: String = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----", - createdAt: Instant = Instant.now(), - keyId: String = "https://$domain/users/$id#pubkey", - followers: String = "https://$domain/users/$id/followers", - following: String = "https://$domain/users/$id/following", - ): Actor { - return actorBuilder.of( - id = id, - name = name, - domain = domain, - screenName = screenName, - description = description, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = privateKey, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - locked = false, - instance = 0 - ) - } - - fun remoteUserOf( - id: Long = generateId(), - name: String = "test-user-$id", - domain: String = "remote.example.com", - screenName: String = name, - description: String = "This user is test user.", - inbox: String = "https://$domain/$id/inbox", - outbox: String = "https://$domain/$id/outbox", - url: String = "https://$domain/$id/", - publicKey: String = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", - createdAt: Instant = Instant.now(), - keyId: String = "https://$domain/$id#pubkey", - followers: String = "https://$domain/$id/followers", - following: String = "https://$domain/$id/following", - instanceId: Long = generateId(), - ): Actor { - return actorBuilder.of( - id = id, - name = name, - domain = domain, - screenName = screenName, - description = description, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = null, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - locked = false, - instance = instanceId - ) - } - - private fun generateId(): Long = runBlocking { - idGenerator.generateId() - } -} diff --git a/hideout-mastodon/build.gradle.kts b/hideout-mastodon/build.gradle.kts new file mode 100644 index 00000000..54fea879 --- /dev/null +++ b/hideout-mastodon/build.gradle.kts @@ -0,0 +1,69 @@ +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask + +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.openapi.generator) + alias(libs.plugins.spring.boot) + alias(libs.plugins.kotlin.spring) +} + + +apply { + plugin("io.spring.dependency-management") +} + +group = "dev.usbharu" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") + + implementation("dev.usbharu:hideout-core:0.0.1") + + implementation(libs.jackson.databind) + implementation(libs.jackson.module.kotlin) + implementation(libs.jakarta.annotation) + implementation(libs.jakarta.validation) + + implementation(libs.bundles.exposed) + implementation(libs.bundles.openapi) + implementation(libs.bundles.coroutines) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} + +tasks.create("openApiGenerateMastodonCompatibleApi", GenerateTask::class) { + generatorName.set("kotlin-spring") + inputSpec.set("$rootDir/src/main/resources/openapi/mastodon.yaml") + outputDir.set("$buildDir/generated/sources/mastodon") + apiPackage.set("dev.usbharu.hideout.mastodon.interfaces.api.generated") + modelPackage.set("dev.usbharu.hideout.mastodon.interfaces.api.generated.model") + configOptions.put("interfaceOnly", "true") + configOptions.put("useSpringBoot3", "true") + configOptions.put("reactive", "true") + configOptions.put("gradleBuildFile", "false") + configOptions.put("useSwaggerUI", "false") + configOptions.put("enumPropertyNaming", "UPPERCASE") + additionalProperties.put("useTags", "true") + + importMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") + typeMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") + templateDir.set("$rootDir/templates") +} + +sourceSets.main { + kotlin.srcDirs( + "$buildDir/generated/sources/mastodon/src/main/kotlin" + ) +} diff --git a/hideout-mastodon/gradle/wrapper/gradle-wrapper.jar b/hideout-mastodon/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/hideout-mastodon/gradle/wrapper/gradle-wrapper.jar differ diff --git a/hideout-mastodon/gradle/wrapper/gradle-wrapper.properties b/hideout-mastodon/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7189c700 --- /dev/null +++ b/hideout-mastodon/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jun 01 12:20:42 JST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/hideout-mastodon/gradlew b/hideout-mastodon/gradlew new file mode 100644 index 00000000..1b6c7873 --- /dev/null +++ b/hideout-mastodon/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/hideout-mastodon/gradlew.bat b/hideout-mastodon/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/hideout-mastodon/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hideout-worker/settings.gradle.kts b/hideout-mastodon/settings.gradle.kts similarity index 76% rename from hideout-worker/settings.gradle.kts rename to hideout-mastodon/settings.gradle.kts index cf826ffd..9334d94c 100644 --- a/hideout-worker/settings.gradle.kts +++ b/hideout-mastodon/settings.gradle.kts @@ -1,7 +1,9 @@ plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } -rootProject.name = "hideout-worker" +rootProject.name = "hideout-mastodon" + +includeBuild("../hideout-core") dependencyResolutionManagement { repositories { @@ -13,6 +15,4 @@ dependencyResolutionManagement { from(files("../libs.versions.toml")) } } -} - -includeBuild("../hideout-core") \ No newline at end of file +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt new file mode 100644 index 00000000..340607b9 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt @@ -0,0 +1,19 @@ +/* + * 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.mastodon.application.accounts + +data class GetAccount(val accountId: String) diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt new file mode 100644 index 00000000..5fb7249f --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt @@ -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.mastodon.application.accounts + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetAccountApplicationService(private val accountQueryService: AccountQueryService, transaction: Transaction) : + AbstractApplicationService( + transaction, + logger + ) { + override suspend fun internalExecute(command: GetAccount, executor: CommandExecutor): Account { + return accountQueryService.findById(command.accountId.toLong()) ?: throw Exception("Account not found") + } + + companion object { + private val logger = LoggerFactory.getLogger(GetAccountApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt new file mode 100644 index 00000000..6baf4976 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1.kt @@ -0,0 +1,19 @@ +/* + * 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.mastodon.application.filter + +data class DeleteFilterV1(val filterKeywordId: Long) diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt new file mode 100644 index 00000000..88134308 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt @@ -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.mastodon.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class DeleteFilterV1ApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(DeleteFilterV1ApplicationService::class.java) + } + + override suspend fun internalExecute(command: DeleteFilterV1, executor: CommandExecutor) { + val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId)) + ?: throw Exception("Not Found") + filterRepository.delete(filter) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt new file mode 100644 index 00000000..e6d1c26c --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1.kt @@ -0,0 +1,19 @@ +/* + * 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.mastodon.application.filter + +data class GetFilterV1(val filterKeywordId: Long) \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt new file mode 100644 index 00000000..0480e70e --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt @@ -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.mastodon.application.filter + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext.* +import dev.usbharu.hideout.core.domain.model.filter.FilterKeywordId +import dev.usbharu.hideout.core.domain.model.filter.FilterMode +import dev.usbharu.hideout.core.domain.model.filter.FilterRepository +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.V1Filter +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class GetFilterV1ApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) : + AbstractApplicationService( + transaction, logger + ) { + override suspend fun internalExecute(command: GetFilterV1, executor: CommandExecutor): V1Filter { + val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId)) + ?: throw Exception("Not Found") + + val filterKeyword = filter.filterKeywords.find { it.id.id == command.filterKeywordId } + return V1Filter( + id = filter.id.id.toString(), + phrase = filterKeyword?.keyword?.keyword, + context = filter.filterContext.map { + when (it) { + HOME -> V1Filter.Context.home + NOTIFICATION -> V1Filter.Context.notifications + PUBLIC -> V1Filter.Context.public + THREAD -> V1Filter.Context.thread + ACCOUNT -> V1Filter.Context.account + } + }, + expiresAt = null, + irreversible = false, + wholeWord = filterKeyword?.mode == FilterMode.WHOLE_WORD + ) + } + + companion object { + private val logger = LoggerFactory.getLogger(GetFilterV1ApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatus.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatus.kt new file mode 100644 index 00000000..37b6882e --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatus.kt @@ -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.mastodon.application.status + +data class GetStatus( + val id: String, +) diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatusApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatusApplicationService.kt new file mode 100644 index 00000000..545bca34 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/status/GetStatusApplicationService.kt @@ -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.mastodon.application.status + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import dev.usbharu.hideout.mastodon.query.StatusQueryService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetStatusApplicationService( + private val statusQueryService: StatusQueryService, + transaction: Transaction, +) : AbstractApplicationService( + transaction, + logger +) { + companion object { + val logger = LoggerFactory.getLogger(GetStatusApplicationService::class.java)!! + } + + override suspend fun internalExecute(command: GetStatus, executor: CommandExecutor): Status { + return statusQueryService.findByPostId(command.id.toLong()) ?: throw Exception("Not fount") + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonSecurityConfig.kt similarity index 65% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonSecurityConfig.kt index c4bce048..513994b1 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonSecurityConfig.kt @@ -16,21 +16,19 @@ package dev.usbharu.hideout.mastodon.config -import dev.usbharu.hideout.application.infrastructure.springframework.RoleHierarchyAuthorizationManagerFactory +import dev.usbharu.hideout.mastodon.external.RoleHierarchyAuthorizationManagerFactory import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.annotation.Order import org.springframework.http.HttpMethod.* -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.invoke import org.springframework.security.web.SecurityFilterChain @Configuration -class MastodonApiSecurityConfig { +class MastodonSecurityConfig { @Bean - @Order(4) + @Order(2) @Suppress("LongMethod") fun mastodonApiSecurityFilterChain( http: HttpSecurity, @@ -111,63 +109,4 @@ class MastodonApiSecurityConfig { return http.build() } - - @Bean - fun roleHierarchy(): RoleHierarchy { - val roleHierarchyImpl = RoleHierarchyImpl() - - roleHierarchyImpl.setHierarchy( - """ - 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 - } -} +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/springframework/RoleHierarchyAuthorizationManagerFactory.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/external/RoleHierarchyAuthorizationManagerFactory.kt similarity index 94% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/springframework/RoleHierarchyAuthorizationManagerFactory.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/external/RoleHierarchyAuthorizationManagerFactory.kt index 99976547..66395c20 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/springframework/RoleHierarchyAuthorizationManagerFactory.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/external/RoleHierarchyAuthorizationManagerFactory.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package dev.usbharu.hideout.application.infrastructure.springframework +package dev.usbharu.hideout.mastodon.external import org.springframework.security.access.hierarchicalroles.RoleHierarchy import org.springframework.security.authorization.AuthorityAuthorizationManager @@ -29,4 +29,4 @@ class RoleHierarchyAuthorizationManagerFactory(private val roleHierarchy: RoleHi hasAuthority.setRoleHierarchy(roleHierarchy) return hasAuthority } -} +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryService.kt similarity index 90% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryService.kt index 75cea253..41ca4ab2 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryService.kt @@ -16,14 +16,13 @@ package dev.usbharu.hideout.mastodon.infrastructure.exposedquery -import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account import dev.usbharu.hideout.mastodon.query.AccountQueryService import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.selectAll import org.springframework.stereotype.Repository -import java.time.Instant @Repository class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService { @@ -43,7 +42,7 @@ class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) } private fun toAccount( - resultRow: ResultRow + resultRow: ResultRow, ): Account { val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}" @@ -64,11 +63,11 @@ class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) bot = false, group = false, discoverable = true, - createdAt = Instant.ofEpochMilli(resultRow[Actors.createdAt]).toString(), + createdAt = resultRow[Actors.createdAt].toString(), lastStatusAt = resultRow[Actors.lastPostAt]?.toString(), statusesCount = resultRow[Actors.postsCount], followersCount = resultRow[Actors.followersCount], followingCount = resultRow[Actors.followingCount], ) } -} +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt similarity index 71% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt index 3194f368..09265d67 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt @@ -16,23 +16,23 @@ package dev.usbharu.hideout.mastodon.infrastructure.exposedquery -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments +import dev.usbharu.hideout.core.domain.model.media.* +import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.infrastructure.exposedrepository.* -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.Status.Visibility.* -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.MediaAttachment +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status.Visibility.* +import dev.usbharu.hideout.mastodon.query.StatusQuery import dev.usbharu.hideout.mastodon.query.StatusQueryService import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.selectAll import org.springframework.stereotype.Repository -import java.time.Instant -import dev.usbharu.hideout.domain.mastodon.model.generated.CustomEmoji as MastodonEmoji +import java.net.URI +import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.CustomEmoji as MastodonEmoji @Suppress("IncompleteDestructuring") @Repository @@ -79,8 +79,7 @@ class StatusQueryServiceImpl : StatusQueryService { pinned: Boolean, tagged: String?, includeFollowers: Boolean, - page: Page - ): PaginationList { + ): List { val query = Posts .leftJoin(PostsMedia) .leftJoin(Actors) @@ -97,13 +96,12 @@ class StatusQueryServiceImpl : StatusQueryService { query.andWhere { Posts.repostId.isNotNull() } } if (includeFollowers) { - query.andWhere { Posts.visibility inList listOf(public.ordinal, unlisted.ordinal, private.ordinal) } + query.andWhere { Posts.visibility inList listOf(public.name, unlisted.name, private.name) } } else { - query.andWhere { Posts.visibility inList listOf(public.ordinal, unlisted.ordinal) } + query.andWhere { Posts.visibility inList listOf(public.name, unlisted.name) } } val pairs = query - .withPagination(page, Posts.id) .groupBy { it[Posts.id] } .map { it.value } .map { @@ -115,11 +113,7 @@ class StatusQueryServiceImpl : StatusQueryService { } val statuses = resolveReplyAndRepost(pairs) - return PaginationList( - statuses, - statuses.firstOrNull()?.id?.toLongOrNull(), - statuses.lastOrNull()?.id?.toLongOrNull() - ) + return statuses } override suspend fun findByPostId(id: Long): Status? { @@ -187,8 +181,8 @@ class StatusQueryServiceImpl : StatusQueryService { private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji( shortcode = this.name, - url = this.url, - staticUrl = this.url, + url = this.url.toString(), + staticUrl = this.url.toString(), visibleInPicker = true, category = this.category.orEmpty() ) @@ -196,7 +190,7 @@ private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji( private fun toStatus(it: ResultRow) = Status( id = it[Posts.id].toString(), uri = it[Posts.apId], - createdAt = Instant.ofEpochMilli(it[Posts.createdAt]).toString(), + createdAt = it[Posts.createdAt].toString(), account = Account( id = it[Actors.id].toString(), username = it[Actors.name], @@ -214,23 +208,22 @@ private fun toStatus(it: ResultRow) = Status( bot = false, group = false, discoverable = true, - createdAt = Instant.ofEpochMilli(it[Actors.createdAt]).toString(), + createdAt = it[Actors.createdAt].toString(), lastStatusAt = it[Actors.lastPostAt]?.toString(), statusesCount = it[Actors.postsCount], followersCount = it[Actors.followersCount], followingCount = it[Actors.followingCount], noindex = false, moved = false, - suspendex = false, + suspended = false, limited = false ), content = it[Posts.text], - visibility = when (it[Posts.visibility]) { - 0 -> public - 1 -> unlisted - 2 -> private - 3 -> direct - else -> public + visibility = when (Visibility.valueOf(it[Posts.visibility])) { + Visibility.PUBLIC -> public + Visibility.UNLISTED -> unlisted + Visibility.FOLLOWERS -> private + Visibility.DIRECT -> direct }, sensitive = it[Posts.sensitive], spoilerText = it[Posts.overview].orEmpty(), @@ -248,3 +241,51 @@ private fun toStatus(it: ResultRow) = Status( text = it[Posts.text], editedAt = null ) + +fun ResultRow.toMedia(): EntityMedia { + val fileType = FileType.valueOf(this[Media.type]) + val mimeType = this[Media.mimeType] + return EntityMedia( + id = MediaId(this[Media.id]), + name = MediaName(this[Media.name]), + url = URI.create(this[Media.url]), + remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) }, + thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) }, + type = fileType, + blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) }, + mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), + description = this[Media.description]?.let { MediaDescription(it) } + ) +} + +fun ResultRow.toMediaOrNull(): EntityMedia? { + val fileType = FileType.valueOf(this.getOrNull(Media.type) ?: return null) + val mimeType = this.getOrNull(Media.mimeType) ?: return null + return EntityMedia( + id = MediaId(this.getOrNull(Media.id) ?: return null), + name = MediaName(this.getOrNull(Media.name) ?: return null), + url = URI.create(this.getOrNull(Media.url) ?: return null), + remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) }, + thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) }, + type = FileType.valueOf(this[Media.type]), + blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) }, + mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), + description = MediaDescription(this[Media.description] ?: return null) + ) +} + +fun EntityMedia.toMediaAttachments(): MediaAttachment = MediaAttachment( + id = id.toString(), + type = when (type) { + FileType.Image -> MediaAttachment.Type.image + FileType.Video -> MediaAttachment.Type.video + FileType.Audio -> MediaAttachment.Type.audio + FileType.Unknown -> MediaAttachment.Type.unknown + }, + url = url.toString(), + previewUrl = thumbnailUrl?.toString(), + remoteUrl = remoteUrl?.toString(), + description = description?.description, + blurhash = blurHash?.hash, + textUrl = url.toString() +) \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt new file mode 100644 index 00000000..7f207ce7 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt @@ -0,0 +1,234 @@ +/* + * 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.mastodon.interfaces.api + +import dev.usbharu.hideout.core.application.actor.GetUserDetail +import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService +import dev.usbharu.hideout.core.application.relationship.acceptfollowrequest.AcceptFollowRequest +import dev.usbharu.hideout.core.application.relationship.acceptfollowrequest.UserAcceptFollowRequestApplicationService +import dev.usbharu.hideout.core.application.relationship.block.Block +import dev.usbharu.hideout.core.application.relationship.block.UserBlockApplicationService +import dev.usbharu.hideout.core.application.relationship.followrequest.FollowRequest +import dev.usbharu.hideout.core.application.relationship.followrequest.UserFollowRequestApplicationService +import dev.usbharu.hideout.core.application.relationship.get.GetRelationship +import dev.usbharu.hideout.core.application.relationship.get.GetRelationshipApplicationService +import dev.usbharu.hideout.core.application.relationship.mute.Mute +import dev.usbharu.hideout.core.application.relationship.mute.UserMuteApplicationService +import dev.usbharu.hideout.core.application.relationship.rejectfollowrequest.RejectFollowRequest +import dev.usbharu.hideout.core.application.relationship.rejectfollowrequest.UserRejectFollowRequestApplicationService +import dev.usbharu.hideout.core.application.relationship.removefromfollowers.RemoveFromFollowers +import dev.usbharu.hideout.core.application.relationship.removefromfollowers.UserRemoveFromFollowersApplicationService +import dev.usbharu.hideout.core.application.relationship.unblock.Unblock +import dev.usbharu.hideout.core.application.relationship.unblock.UserUnblockApplicationService +import dev.usbharu.hideout.core.application.relationship.unfollow.Unfollow +import dev.usbharu.hideout.core.application.relationship.unfollow.UserUnfollowApplicationService +import dev.usbharu.hideout.core.application.relationship.unmute.Unmute +import dev.usbharu.hideout.core.application.relationship.unmute.UserUnmuteApplicationService +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutor +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import dev.usbharu.hideout.mastodon.application.accounts.GetAccount +import dev.usbharu.hideout.mastodon.application.accounts.GetAccountApplicationService +import dev.usbharu.hideout.mastodon.interfaces.api.generated.AccountApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class SpringAccountApi( + private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, + private val getUserDetailApplicationService: GetUserDetailApplicationService, + private val getAccountApplicationService: GetAccountApplicationService, + private val userFollowRequestApplicationService: UserFollowRequestApplicationService, + private val getRelationshipApplicationService: GetRelationshipApplicationService, + private val userBlockApplicationService: UserBlockApplicationService, + private val userUnblockApplicationService: UserUnblockApplicationService, + private val userMuteApplicationService: UserMuteApplicationService, + private val userUnmuteApplicationService: UserUnmuteApplicationService, + private val userAcceptFollowRequestApplicationService: UserAcceptFollowRequestApplicationService, + private val userRejectFollowRequestApplicationService: UserRejectFollowRequestApplicationService, + private val userRemoveFromFollowersApplicationService: UserRemoveFromFollowersApplicationService, + private val userUnfollowApplicationService: UserUnfollowApplicationService, +) : AccountApi { + + + override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userBlockApplicationService.execute(Block(id.toLong()), executor) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsIdFollowPost( + id: String, + followRequestBody: FollowRequestBody?, + ): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userFollowRequestApplicationService.execute( + FollowRequest(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + private suspend fun fetchRelationship( + id: String, + executor: Oauth2CommandExecutor, + ): ResponseEntity { + val relationship = getRelationshipApplicationService.execute(GetRelationship(id.toLong()), executor) + return ResponseEntity.ok( + Relationship( + id = relationship.targetId.toString(), + following = relationship.following, + showingReblogs = true, + notifying = false, + followedBy = relationship.followedBy, + blocking = relationship.blocking, + blockedBy = relationship.blockedBy, + muting = relationship.muting, + mutingNotifications = false, + requested = relationship.followRequesting, + domainBlocking = relationship.domainBlocking, + endorsed = false, + note = "" + ) + ) + } + + override suspend fun apiV1AccountsIdGet(id: String): ResponseEntity { + return ResponseEntity.ok( + getAccountApplicationService.execute( + GetAccount(id), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) + } + + override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userMuteApplicationService.execute( + Mute(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsIdRemoveFromFollowersPost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userRemoveFromFollowersApplicationService.execute( + RemoveFromFollowers(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userUnblockApplicationService.execute( + Unblock(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userUnfollowApplicationService.execute( + Unfollow(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsIdUnmutePost(id: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userUnmuteApplicationService.execute( + Unmute(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + override suspend fun apiV1AccountsPost(accountsCreateRequest: AccountsCreateRequest): ResponseEntity { + return super.apiV1AccountsPost(accountsCreateRequest) + } + + override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): ResponseEntity { + return super.apiV1AccountsUpdateCredentialsPatch(updateCredentials) + } + + override suspend fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity { + val commandExecutor = oauth2CommandExecutorFactory.getCommandExecutor() + val localActor = + getUserDetailApplicationService.execute(GetUserDetail(commandExecutor.userDetailId), commandExecutor) + + return ResponseEntity.ok( + CredentialAccount( + id = localActor.id.toString(), + username = localActor.name, + acct = localActor.name + "@" + localActor.domain, + url = localActor.url, + displayName = localActor.screenName, + note = localActor.description, + avatar = localActor.iconUrl, + avatarStatic = localActor.iconUrl, + header = localActor.iconUrl, + headerStatic = localActor.iconUrl, + locked = localActor.locked, + fields = emptyList(), + emojis = localActor.emojis.map { + CustomEmoji( + shortcode = it.name, + url = it.url.toString(), + staticUrl = it.url.toString(), + true, + category = it.category.orEmpty() + ) + }, + bot = false, + group = false, + discoverable = true, + createdAt = localActor.createdAt.toString(), + lastStatusAt = localActor.lastPostAt?.toString(), + statusesCount = localActor.postsCount, + followersCount = localActor.followersCount, + followingCount = localActor.followingCount, + moved = localActor.moveTo != null, + noindex = true, + suspendex = localActor.suspend, + limited = false, + role = null, + source = AccountSource( + localActor.description, + emptyList(), + AccountSource.Privacy.`public`, + false, + 0 + ) + ) + ) + } + + override suspend fun apiV1FollowRequestsAccountIdAuthorizePost(accountId: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userAcceptFollowRequestApplicationService.execute( + AcceptFollowRequest(accountId.toLong()), executor + ) + return fetchRelationship(accountId, executor) + } + + override suspend fun apiV1FollowRequestsAccountIdRejectPost(accountId: String): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userRejectFollowRequestApplicationService.execute( + RejectFollowRequest(accountId.toLong()), executor + ) + return fetchRelationship(accountId, executor) + } + +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAppApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAppApi.kt new file mode 100644 index 00000000..cc870055 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAppApi.kt @@ -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.mastodon.interfaces.api + +import dev.usbharu.hideout.core.application.application.RegisterApplication +import dev.usbharu.hideout.core.application.application.RegisterApplicationApplicationService +import dev.usbharu.hideout.mastodon.interfaces.api.generated.AppApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Application +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.AppsRequest +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller +import java.net.URI + +@Controller +class SpringAppApi(private val registerApplicationApplicationService: RegisterApplicationApplicationService) : AppApi { + override suspend fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity { + + val registerApplication = RegisterApplication( + appsRequest.clientName, + setOf(URI.create(appsRequest.redirectUris)), + false, + appsRequest.scopes?.split(" ").orEmpty().toSet().ifEmpty { setOf("read") } + ) + val registeredApplication = registerApplicationApplicationService.register(registerApplication) + return ResponseEntity.ok( + Application( + registeredApplication.name, + "invalid-vapid-key", + null, + registeredApplication.clientId, + registeredApplication.clientSecret, + appsRequest.redirectUris + ) + ) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt new file mode 100644 index 00000000..8d1c6f41 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt @@ -0,0 +1,236 @@ +/* + * 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.mastodon.interfaces.api + +import dev.usbharu.hideout.core.application.filter.* +import dev.usbharu.hideout.core.domain.model.filter.FilterAction +import dev.usbharu.hideout.core.domain.model.filter.FilterContext +import dev.usbharu.hideout.core.domain.model.filter.FilterMode +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import dev.usbharu.hideout.mastodon.application.filter.DeleteFilterV1 +import dev.usbharu.hideout.mastodon.application.filter.DeleteFilterV1ApplicationService +import dev.usbharu.hideout.mastodon.application.filter.GetFilterV1 +import dev.usbharu.hideout.mastodon.application.filter.GetFilterV1ApplicationService +import dev.usbharu.hideout.mastodon.interfaces.api.generated.FilterApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Filter +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterKeyword +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterPostRequest.Context +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.V1FilterPostRequest.Context.* +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class SpringFilterApi( + private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, + private val userRegisterFilterApplicationService: UserRegisterFilterApplicationService, + private val getFilterV1ApplicationService: GetFilterV1ApplicationService, + private val deleteFilterV1ApplicationService: DeleteFilterV1ApplicationService, + private val userDeleteFilterApplicationService: UserDeleteFilterApplicationService, + private val userGetFilterApplicationService: UserGetFilterApplicationService, +) : FilterApi { + + override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity { + return ResponseEntity.ok( + deleteFilterV1ApplicationService.execute( + DeleteFilterV1(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) + } + + override suspend fun apiV1FiltersIdGet(id: String): ResponseEntity { + return ResponseEntity.ok( + getFilterV1ApplicationService.execute( + GetFilterV1(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) + } + + override suspend fun apiV1FiltersIdPut( + id: String, + phrase: String?, + context: List?, + irreversible: Boolean?, + wholeWord: Boolean?, + expiresIn: Int?, + ): ResponseEntity { + return super.apiV1FiltersIdPut(id, phrase, context, irreversible, wholeWord, expiresIn) + } + + override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + val filterMode = if (v1FilterPostRequest.wholeWord == true) { + FilterMode.WHOLE_WORD + } else { + FilterMode.NONE + } + val filterContext = v1FilterPostRequest.context.map { + when (it) { + home -> FilterContext.HOME + notifications -> FilterContext.NOTIFICATION + public -> FilterContext.PUBLIC + thread -> FilterContext.THREAD + account -> FilterContext.ACCOUNT + } + }.toSet() + val filter = userRegisterFilterApplicationService.execute( + RegisterFilter( + v1FilterPostRequest.phrase, filterContext, FilterAction.WARN, + setOf(RegisterFilterKeyword(v1FilterPostRequest.phrase, filterMode)) + ), executor + ) + return ResponseEntity.ok( + getFilterV1ApplicationService.execute( + GetFilterV1(filter.filterKeywords.first().id), + executor + ) + ) + } + + override suspend fun apiV2FiltersFilterIdKeywordsPost( + filterId: String, + filterKeywordsPostRequest: FilterKeywordsPostRequest, + ): ResponseEntity { + return super.apiV2FiltersFilterIdKeywordsPost(filterId, filterKeywordsPostRequest) + } + + override suspend fun apiV2FiltersFilterIdStatusesPost( + filterId: String, + filterStatusRequest: FilterStatusRequest, + ): ResponseEntity { + return super.apiV2FiltersFilterIdStatusesPost(filterId, filterStatusRequest) + } + + override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity { + userDeleteFilterApplicationService.execute( + DeleteFilter(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + return ResponseEntity.ok(Unit) + } + + override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity { + val filter = userGetFilterApplicationService.execute( + GetFilter(id.toLong()), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + return ResponseEntity.ok( + filter(filter) + ) + } + + private fun filter(filter: dev.usbharu.hideout.core.application.filter.Filter) = Filter( + id = filter.filterId.toString(), + title = filter.name, + context = filter.filterContext.map { + when (it) { + FilterContext.HOME -> Filter.Context.home + FilterContext.NOTIFICATION -> Filter.Context.notifications + FilterContext.PUBLIC -> Filter.Context.public + FilterContext.THREAD -> Filter.Context.thread + FilterContext.ACCOUNT -> Filter.Context.account + } + }, + expiresAt = null, + filterAction = when (filter.filterAction) { + FilterAction.WARN -> Filter.FilterAction.warn + FilterAction.HIDE -> Filter.FilterAction.hide + + }, + keywords = filter.filterKeywords.map { + FilterKeyword( + it.id.toString(), + it.keyword, + it.filterMode == FilterMode.WHOLE_WORD + ) + }, statuses = null + ) + + override suspend fun apiV2FiltersIdPut( + id: String, + title: String?, + context: List?, + filterAction: String?, + expiresIn: Int?, + keywordsAttributes: List?, + ): ResponseEntity { + return super.apiV2FiltersIdPut(id, title, context, filterAction, expiresIn, keywordsAttributes) + } + + override suspend fun apiV2FiltersKeywordsIdDelete(id: String): ResponseEntity { + return super.apiV2FiltersKeywordsIdDelete(id) + } + + override suspend fun apiV2FiltersKeywordsIdGet(id: String): ResponseEntity { + return super.apiV2FiltersKeywordsIdGet(id) + } + + override suspend fun apiV2FiltersKeywordsIdPut( + id: String, + keyword: String?, + wholeWord: Boolean?, + regex: Boolean?, + ): ResponseEntity { + return super.apiV2FiltersKeywordsIdPut(id, keyword, wholeWord, regex) + } + + override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity { + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + val filter = userRegisterFilterApplicationService.execute( + RegisterFilter( + filterName = filterPostRequest.title, + filterContext = filterPostRequest.context.map { + when (it) { + Context.home -> FilterContext.HOME + Context.notifications -> FilterContext.NOTIFICATION + Context.public -> FilterContext.PUBLIC + Context.thread -> FilterContext.THREAD + Context.account -> FilterContext.ACCOUNT + } + }.toSet(), + filterAction = when (filterPostRequest.filterAction) { + FilterPostRequest.FilterAction.warn -> FilterAction.WARN + FilterPostRequest.FilterAction.hide -> FilterAction.HIDE + null -> FilterAction.WARN + }, + filterKeywords = filterPostRequest.keywordsAttributes.orEmpty().map { + RegisterFilterKeyword( + it.keyword, + if (it.regex == true) { + FilterMode.REGEX + } else if (it.wholeWord == true) { + FilterMode.WHOLE_WORD + } else { + FilterMode.NONE + } + ) + }.toSet() + ), executor + ) + return ResponseEntity.ok(filter(filter)) + } + + override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity { + return ResponseEntity.notFound().build() + } + + override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity { + return ResponseEntity.notFound().build() + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringInstanceApi.kt similarity index 55% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringInstanceApi.kt index 220625ce..b4aa3cf8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringInstanceApi.kt @@ -14,17 +14,26 @@ * limitations under the License. */ -package dev.usbharu.hideout.mastodon.interfaces.api.instance +package dev.usbharu.hideout.mastodon.interfaces.api -import dev.usbharu.hideout.controller.mastodon.generated.InstanceApi -import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance -import dev.usbharu.hideout.mastodon.service.instance.InstanceApiService -import org.springframework.http.HttpStatus +import dev.usbharu.hideout.mastodon.interfaces.api.generated.InstanceApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* +import kotlinx.coroutines.flow.Flow import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller @Controller -class MastodonInstanceApiController(private val instanceApiService: InstanceApiService) : InstanceApi { - override suspend fun apiV1InstanceGet(): ResponseEntity = - ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK) -} +class SpringInstanceApi : InstanceApi { + + override suspend fun apiV1InstanceExtendedDescriptionGet(): ResponseEntity { + return super.apiV1InstanceExtendedDescriptionGet() + } + + override suspend fun apiV1InstanceGet(): ResponseEntity { + return super.apiV1InstanceGet() + } + + override suspend fun apiV2InstanceGet(): ResponseEntity { + return super.apiV2InstanceGet() + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt new file mode 100644 index 00000000..a3a84a79 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt @@ -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.mastodon.interfaces.api + +import dev.usbharu.hideout.core.application.media.UploadMedia +import dev.usbharu.hideout.core.application.media.UploadMediaApplicationService +import dev.usbharu.hideout.core.domain.model.media.FileType.* +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import dev.usbharu.hideout.mastodon.interfaces.api.generated.MediaApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.MediaAttachment +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller +import org.springframework.web.multipart.MultipartFile +import java.nio.file.Files + +@Controller +class SpringMediaApi( + private val uploadMediaApplicationService: UploadMediaApplicationService, + private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory +) : MediaApi { + override suspend fun apiV1MediaPost( + file: MultipartFile, + thumbnail: MultipartFile?, + description: String?, + focus: String?, + ): ResponseEntity { + val tempFile = Files.createTempFile("hideout-tmp-file", ".tmp") + + Files.newOutputStream(tempFile).use { outputStream -> + file.inputStream.use { + it.transferTo(outputStream) + } + } + + val media = uploadMediaApplicationService.execute( + UploadMedia( + tempFile, + file.originalFilename ?: file.name, + null, + description + ), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + + return ResponseEntity.ok( + MediaAttachment( + media.id.toString(), + when (media.type) { + Image -> MediaAttachment.Type.image + Video -> MediaAttachment.Type.video + Audio -> MediaAttachment.Type.audio + Unknown -> MediaAttachment.Type.unknown + }, + media.url.toString(), + media.thumbprintURI?.toString(), + media.remoteURL?.toString(), + media.description, + media.blurHash, + media.url.toASCIIString() + ) + ) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringNotificationApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringNotificationApi.kt new file mode 100644 index 00000000..cd196658 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringNotificationApi.kt @@ -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.mastodon.interfaces.api + +import dev.usbharu.hideout.mastodon.interfaces.api.generated.NotificationsApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Notification +import kotlinx.coroutines.flow.Flow +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class SpringNotificationApi : NotificationsApi { + override suspend fun apiV1NotificationsClearPost(): ResponseEntity { + return super.apiV1NotificationsClearPost() + } + + override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity { + return super.apiV1NotificationsIdDismissPost(id) + } + + override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity { + return super.apiV1NotificationsIdGet(id) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringStatusApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringStatusApi.kt new file mode 100644 index 00000000..322d3f68 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringStatusApi.kt @@ -0,0 +1,85 @@ +/* + * 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.mastodon.interfaces.api + +import dev.usbharu.hideout.core.application.post.RegisterLocalPost +import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService +import dev.usbharu.hideout.core.domain.model.post.Visibility +import dev.usbharu.hideout.core.infrastructure.springframework.DelegateCommandExecutorFactory +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutor +import dev.usbharu.hideout.mastodon.application.status.GetStatus +import dev.usbharu.hideout.mastodon.application.status.GetStatusApplicationService +import dev.usbharu.hideout.mastodon.interfaces.api.generated.StatusApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest.Visibility.* +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class SpringStatusApi( + private val delegateCommandExecutorFactory: DelegateCommandExecutorFactory, + private val registerLocalPostApplicationService: RegisterLocalPostApplicationService, + private val getStatusApplicationService: GetStatusApplicationService, +) : StatusApi { + override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity { + return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji) + } + + override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity { + return super.apiV1StatusesIdEmojiReactionsEmojiPut(id, emoji) + } + + override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { + + return ResponseEntity.ok( + getStatusApplicationService.execute( + GetStatus(id), + delegateCommandExecutorFactory.getCommandExecutor() + ) + ) + } + + override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity { + val executor = delegateCommandExecutorFactory.getCommandExecutor() as Oauth2CommandExecutor + val execute = registerLocalPostApplicationService.execute( + RegisterLocalPost( + userDetailId = executor.userDetailId, + content = statusesRequest.status.orEmpty(), + overview = statusesRequest.spoilerText, + visibility = when (statusesRequest.visibility) { + public -> Visibility.PUBLIC + unlisted -> Visibility.UNLISTED + private -> Visibility.FOLLOWERS + direct -> Visibility.DIRECT + null -> Visibility.PUBLIC + }, + repostId = null, + replyId = statusesRequest.inReplyToId?.toLong(), + sensitive = statusesRequest.sensitive == true, + mediaIds = statusesRequest.mediaIds.orEmpty().map { it.toLong() } + ), + executor + ) + + + val status = getStatusApplicationService.execute(GetStatus(execute.toString()), executor) + return ResponseEntity.ok( + status + ) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt similarity index 72% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt index e925aef0..2f631d70 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.service.activity.reject +package dev.usbharu.hideout.mastodon.interfaces.api -import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.mastodon.interfaces.api.generated.TimelineApi +import org.springframework.stereotype.Controller -interface ApSendRejectService { - suspend fun sendRejectFollow(actor: Actor, target: Actor) -} +@Controller +class SpringTimelineApi : TimelineApi \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt similarity index 91% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt index 61b49950..61de616c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt @@ -16,9 +16,9 @@ package dev.usbharu.hideout.mastodon.query -import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account interface AccountQueryService { suspend fun findById(accountId: Long): Account? suspend fun findByIds(accountIds: List): List -} +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt similarity index 77% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt rename to hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt index e5640509..dc7cc88e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt @@ -16,10 +16,7 @@ package dev.usbharu.hideout.mastodon.query -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status interface StatusQueryService { suspend fun findByPostIds(ids: List): List @@ -34,8 +31,15 @@ interface StatusQueryService { pinned: Boolean = false, tagged: String?, includeFollowers: Boolean = false, - page: Page - ): PaginationList + ): List suspend fun findByPostId(id: Long): Status? } + +data class StatusQuery( + val postId: Long, + val replyId: Long?, + val repostId: Long?, + val mediaIds: List, + val emojiIds: List, +) \ No newline at end of file diff --git a/hideout-core/src/main/resources/openapi/mastodon.yaml b/hideout-mastodon/src/main/resources/openapi/mastodon.yaml similarity index 99% rename from hideout-core/src/main/resources/openapi/mastodon.yaml rename to hideout-mastodon/src/main/resources/openapi/mastodon.yaml index e104eff7..bc5195d5 100644 --- a/hideout-core/src/main/resources/openapi/mastodon.yaml +++ b/hideout-mastodon/src/main/resources/openapi/mastodon.yaml @@ -242,6 +242,9 @@ paths: application/json: schema: $ref: "#/components/schemas/AppsRequest" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/AppsRequest" responses: 200: @@ -1537,7 +1540,7 @@ components: type: boolean moved: type: boolean - suspendex: + suspended: type: boolean limited: type: boolean @@ -1574,8 +1577,6 @@ components: - discoverable - created_at - statuses_count - - followers_count - - followers_count CredentialAccount: type: object @@ -1660,8 +1661,6 @@ components: - created_at - last_status_at - statuses_count - - followers_count - - followers_count - source AccountSource: diff --git a/hideout-core/templates/api.mustache b/hideout-mastodon/templates/api.mustache similarity index 100% rename from hideout-core/templates/api.mustache rename to hideout-mastodon/templates/api.mustache diff --git a/hideout-core/templates/apiController.mustache b/hideout-mastodon/templates/apiController.mustache similarity index 100% rename from hideout-core/templates/apiController.mustache rename to hideout-mastodon/templates/apiController.mustache diff --git a/hideout-core/templates/apiDelegate.mustache b/hideout-mastodon/templates/apiDelegate.mustache similarity index 100% rename from hideout-core/templates/apiDelegate.mustache rename to hideout-mastodon/templates/apiDelegate.mustache diff --git a/hideout-core/templates/apiInterface.mustache b/hideout-mastodon/templates/apiInterface.mustache similarity index 100% rename from hideout-core/templates/apiInterface.mustache rename to hideout-mastodon/templates/apiInterface.mustache diff --git a/hideout-core/templates/apiUtil.mustache b/hideout-mastodon/templates/apiUtil.mustache similarity index 100% rename from hideout-core/templates/apiUtil.mustache rename to hideout-mastodon/templates/apiUtil.mustache diff --git a/hideout-core/templates/api_test.mustache b/hideout-mastodon/templates/api_test.mustache similarity index 100% rename from hideout-core/templates/api_test.mustache rename to hideout-mastodon/templates/api_test.mustache diff --git a/hideout-core/templates/beanValidation.mustache b/hideout-mastodon/templates/beanValidation.mustache similarity index 100% rename from hideout-core/templates/beanValidation.mustache rename to hideout-mastodon/templates/beanValidation.mustache diff --git a/hideout-core/templates/beanValidationModel.mustache b/hideout-mastodon/templates/beanValidationModel.mustache similarity index 100% rename from hideout-core/templates/beanValidationModel.mustache rename to hideout-mastodon/templates/beanValidationModel.mustache diff --git a/hideout-core/templates/beanValidationPath.mustache b/hideout-mastodon/templates/beanValidationPath.mustache similarity index 100% rename from hideout-core/templates/beanValidationPath.mustache rename to hideout-mastodon/templates/beanValidationPath.mustache diff --git a/hideout-core/templates/beanValidationPathParams.mustache b/hideout-mastodon/templates/beanValidationPathParams.mustache similarity index 100% rename from hideout-core/templates/beanValidationPathParams.mustache rename to hideout-mastodon/templates/beanValidationPathParams.mustache diff --git a/hideout-core/templates/beanValidationQueryParams.mustache b/hideout-mastodon/templates/beanValidationQueryParams.mustache similarity index 100% rename from hideout-core/templates/beanValidationQueryParams.mustache rename to hideout-mastodon/templates/beanValidationQueryParams.mustache diff --git a/hideout-core/templates/bodyParams.mustache b/hideout-mastodon/templates/bodyParams.mustache similarity index 100% rename from hideout-core/templates/bodyParams.mustache rename to hideout-mastodon/templates/bodyParams.mustache diff --git a/hideout-core/templates/dataClass.mustache b/hideout-mastodon/templates/dataClass.mustache similarity index 67% rename from hideout-core/templates/dataClass.mustache rename to hideout-mastodon/templates/dataClass.mustache index 0fb78397..4695e2a9 100644 --- a/hideout-core/templates/dataClass.mustache +++ b/hideout-mastodon/templates/dataClass.mustache @@ -5,7 +5,8 @@ {{/vars}} */{{#discriminator}} {{>typeInfoAnnotation}}{{/discriminator}} -{{#discriminator}}interface {{classname}}{{/discriminator}}{{^discriminator}}{{#hasVars}}data {{/hasVars}}class {{classname}}( + +{{#discriminator}}interface {{classname}}{{/discriminator}}{{^discriminator}}{{#hasVars}}data {{/hasVars}}class {{classname}} @ConstructorProperties( {{#vars}}"{{baseName}}",{{/vars}} ) constructor( {{#requiredVars}} {{>dataClassReqVar}}{{^-last}}, {{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, @@ -25,9 +26,9 @@ * {{{description}}} * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} */ - enum class {{{nameInCamelCase}}}(val value: {{#isContainer}}{{#items}}{{{dataType}}}{{/items}}{{/isContainer}}{{^isContainer}}{{{dataType}}}{{/isContainer}}) { - {{#allowableValues}}{{#enumVars}} - @JsonProperty({{{value}}}) {{{name}}}({{{value}}}){{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + enum class {{{nameInPascalCase}}}(val value: {{#isContainer}}{{#items}}{{{dataType}}}{{/items}}{{/isContainer}}{{^isContainer}}{{{dataType}}}{{/isContainer}}) { + {{#allowableValues}}{{#values}} + @JsonProperty("{{.}}") `{{.}}`("{{.}}"){{^-last}},{{/-last}}{{/values}}{{/allowableValues}} } {{/isEnum}}{{/vars}}{{/hasEnums}} } diff --git a/hideout-core/templates/dataClassOptVar.mustache b/hideout-mastodon/templates/dataClassOptVar.mustache similarity index 73% rename from hideout-core/templates/dataClassOptVar.mustache rename to hideout-mastodon/templates/dataClassOptVar.mustache index 3ba523bb..e8a4e681 100644 --- a/hideout-core/templates/dataClassOptVar.mustache +++ b/hideout-mastodon/templates/dataClassOptVar.mustache @@ -2,4 +2,4 @@ @Schema({{#example}}example = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{.}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{/example}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}description = "{{{description}}}"){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}} @ApiModelProperty({{#example}}example = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{.}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{/example}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swagger1AnnotationLibrary}}{{#deprecated}} @Deprecated(message = ""){{/deprecated}} -@get:JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{{nameInCamelCase}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}} +@get:JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{#lambda.camelcase}} {{{name}}} {{/lambda.camelcase}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{{nameInPascalCase}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}} diff --git a/hideout-core/templates/dataClassReqVar.mustache b/hideout-mastodon/templates/dataClassReqVar.mustache similarity index 85% rename from hideout-core/templates/dataClassReqVar.mustache rename to hideout-mastodon/templates/dataClassReqVar.mustache index 9ae7d3a9..15d2118a 100644 --- a/hideout-core/templates/dataClassReqVar.mustache +++ b/hideout-mastodon/templates/dataClassReqVar.mustache @@ -1,4 +1,4 @@ {{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swagger2AnnotationLibrary}} @Schema({{#example}}example = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{.}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{/example}}required = true, {{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}description = "{{{description}}}"){{/swagger2AnnotationLibrary}}{{#swagger1AnnotationLibrary}} @ApiModelProperty({{#example}}example = "{{#lambdaRemoveLineBreak}}{{#lambdaEscapeDoubleQuote}}{{{.}}}{{/lambdaEscapeDoubleQuote}}{{/lambdaRemoveLineBreak}}", {{/example}}required = true, {{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swagger1AnnotationLibrary}}{{^isNullable}}@get:NotNull{{/isNullable}} -@get:JsonProperty("{{{baseName}}}", required = true){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{{nameInCamelCase}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}} +@get:JsonProperty("{{{baseName}}}", required = true){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{{nameInPascalCase}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}} diff --git a/hideout-core/templates/enumClass.mustache b/hideout-mastodon/templates/enumClass.mustache similarity index 100% rename from hideout-core/templates/enumClass.mustache rename to hideout-mastodon/templates/enumClass.mustache diff --git a/hideout-core/templates/exceptions.mustache b/hideout-mastodon/templates/exceptions.mustache similarity index 100% rename from hideout-core/templates/exceptions.mustache rename to hideout-mastodon/templates/exceptions.mustache diff --git a/hideout-core/templates/formParams.mustache b/hideout-mastodon/templates/formParams.mustache similarity index 100% rename from hideout-core/templates/formParams.mustache rename to hideout-mastodon/templates/formParams.mustache diff --git a/hideout-core/templates/generatedAnnotation.mustache b/hideout-mastodon/templates/generatedAnnotation.mustache similarity index 100% rename from hideout-core/templates/generatedAnnotation.mustache rename to hideout-mastodon/templates/generatedAnnotation.mustache diff --git a/hideout-core/templates/headerParams.mustache b/hideout-mastodon/templates/headerParams.mustache similarity index 100% rename from hideout-core/templates/headerParams.mustache rename to hideout-mastodon/templates/headerParams.mustache diff --git a/hideout-core/templates/homeController.mustache b/hideout-mastodon/templates/homeController.mustache similarity index 100% rename from hideout-core/templates/homeController.mustache rename to hideout-mastodon/templates/homeController.mustache diff --git a/hideout-core/templates/interfaceOptVar.mustache b/hideout-mastodon/templates/interfaceOptVar.mustache similarity index 100% rename from hideout-core/templates/interfaceOptVar.mustache rename to hideout-mastodon/templates/interfaceOptVar.mustache diff --git a/hideout-core/templates/interfaceReqVar.mustache b/hideout-mastodon/templates/interfaceReqVar.mustache similarity index 100% rename from hideout-core/templates/interfaceReqVar.mustache rename to hideout-mastodon/templates/interfaceReqVar.mustache diff --git a/hideout-core/templates/libraries/spring-boot/README.mustache b/hideout-mastodon/templates/libraries/spring-boot/README.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/README.mustache rename to hideout-mastodon/templates/libraries/spring-boot/README.mustache diff --git a/hideout-core/templates/libraries/spring-boot/application.mustache b/hideout-mastodon/templates/libraries/spring-boot/application.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/application.mustache rename to hideout-mastodon/templates/libraries/spring-boot/application.mustache diff --git a/hideout-core/templates/libraries/spring-boot/buildGradle-sb3-Kts.mustache b/hideout-mastodon/templates/libraries/spring-boot/buildGradle-sb3-Kts.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/buildGradle-sb3-Kts.mustache rename to hideout-mastodon/templates/libraries/spring-boot/buildGradle-sb3-Kts.mustache diff --git a/hideout-core/templates/libraries/spring-boot/buildGradleKts.mustache b/hideout-mastodon/templates/libraries/spring-boot/buildGradleKts.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/buildGradleKts.mustache rename to hideout-mastodon/templates/libraries/spring-boot/buildGradleKts.mustache diff --git a/hideout-core/templates/libraries/spring-boot/defaultBasePath.mustache b/hideout-mastodon/templates/libraries/spring-boot/defaultBasePath.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/defaultBasePath.mustache rename to hideout-mastodon/templates/libraries/spring-boot/defaultBasePath.mustache diff --git a/hideout-core/templates/libraries/spring-boot/pom-sb3.mustache b/hideout-mastodon/templates/libraries/spring-boot/pom-sb3.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/pom-sb3.mustache rename to hideout-mastodon/templates/libraries/spring-boot/pom-sb3.mustache diff --git a/hideout-core/templates/libraries/spring-boot/pom.mustache b/hideout-mastodon/templates/libraries/spring-boot/pom.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/pom.mustache rename to hideout-mastodon/templates/libraries/spring-boot/pom.mustache diff --git a/hideout-core/templates/libraries/spring-boot/settingsGradle.mustache b/hideout-mastodon/templates/libraries/spring-boot/settingsGradle.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/settingsGradle.mustache rename to hideout-mastodon/templates/libraries/spring-boot/settingsGradle.mustache diff --git a/hideout-core/templates/libraries/spring-boot/springBootApplication.mustache b/hideout-mastodon/templates/libraries/spring-boot/springBootApplication.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/springBootApplication.mustache rename to hideout-mastodon/templates/libraries/spring-boot/springBootApplication.mustache diff --git a/hideout-core/templates/libraries/spring-boot/swagger-ui.mustache b/hideout-mastodon/templates/libraries/spring-boot/swagger-ui.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-boot/swagger-ui.mustache rename to hideout-mastodon/templates/libraries/spring-boot/swagger-ui.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/README.mustache b/hideout-mastodon/templates/libraries/spring-cloud/README.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/README.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/README.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/apiClient.mustache b/hideout-mastodon/templates/libraries/spring-cloud/apiClient.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/apiClient.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/apiClient.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/apiKeyRequestInterceptor.mustache b/hideout-mastodon/templates/libraries/spring-cloud/apiKeyRequestInterceptor.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/apiKeyRequestInterceptor.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/apiKeyRequestInterceptor.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/buildGradle-sb3-Kts.mustache b/hideout-mastodon/templates/libraries/spring-cloud/buildGradle-sb3-Kts.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/buildGradle-sb3-Kts.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/buildGradle-sb3-Kts.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/buildGradleKts.mustache b/hideout-mastodon/templates/libraries/spring-cloud/buildGradleKts.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/buildGradleKts.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/buildGradleKts.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/clientConfiguration.mustache b/hideout-mastodon/templates/libraries/spring-cloud/clientConfiguration.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/clientConfiguration.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/clientConfiguration.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/pom-sb3.mustache b/hideout-mastodon/templates/libraries/spring-cloud/pom-sb3.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/pom-sb3.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/pom-sb3.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/pom.mustache b/hideout-mastodon/templates/libraries/spring-cloud/pom.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/pom.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/pom.mustache diff --git a/hideout-core/templates/libraries/spring-cloud/settingsGradle.mustache b/hideout-mastodon/templates/libraries/spring-cloud/settingsGradle.mustache similarity index 100% rename from hideout-core/templates/libraries/spring-cloud/settingsGradle.mustache rename to hideout-mastodon/templates/libraries/spring-cloud/settingsGradle.mustache diff --git a/hideout-core/templates/methodBody.mustache b/hideout-mastodon/templates/methodBody.mustache similarity index 100% rename from hideout-core/templates/methodBody.mustache rename to hideout-mastodon/templates/methodBody.mustache diff --git a/hideout-core/templates/model.mustache b/hideout-mastodon/templates/model.mustache similarity index 96% rename from hideout-core/templates/model.mustache rename to hideout-mastodon/templates/model.mustache index 48ca8ae6..d592ec82 100644 --- a/hideout-core/templates/model.mustache +++ b/hideout-mastodon/templates/model.mustache @@ -20,7 +20,7 @@ import java.util.Objects {{#swagger1AnnotationLibrary}} import io.swagger.annotations.ApiModelProperty {{/swagger1AnnotationLibrary}} - +import java.beans.ConstructorProperties {{#models}} {{#model}} {{#isEnum}}{{>enumClass}}{{/isEnum}}{{^isEnum}}{{>dataClass}}{{/isEnum}} diff --git a/hideout-core/templates/modelMutable.mustache b/hideout-mastodon/templates/modelMutable.mustache similarity index 100% rename from hideout-core/templates/modelMutable.mustache rename to hideout-mastodon/templates/modelMutable.mustache diff --git a/hideout-core/templates/openapi.mustache b/hideout-mastodon/templates/openapi.mustache similarity index 100% rename from hideout-core/templates/openapi.mustache rename to hideout-mastodon/templates/openapi.mustache diff --git a/hideout-core/templates/optionalDataType.mustache b/hideout-mastodon/templates/optionalDataType.mustache similarity index 100% rename from hideout-core/templates/optionalDataType.mustache rename to hideout-mastodon/templates/optionalDataType.mustache diff --git a/hideout-core/templates/pathParams.mustache b/hideout-mastodon/templates/pathParams.mustache similarity index 100% rename from hideout-core/templates/pathParams.mustache rename to hideout-mastodon/templates/pathParams.mustache diff --git a/hideout-core/templates/queryParams.mustache b/hideout-mastodon/templates/queryParams.mustache similarity index 100% rename from hideout-core/templates/queryParams.mustache rename to hideout-mastodon/templates/queryParams.mustache diff --git a/hideout-core/templates/returnTypes.mustache b/hideout-mastodon/templates/returnTypes.mustache similarity index 100% rename from hideout-core/templates/returnTypes.mustache rename to hideout-mastodon/templates/returnTypes.mustache diff --git a/hideout-core/templates/returnValue.mustache b/hideout-mastodon/templates/returnValue.mustache similarity index 100% rename from hideout-core/templates/returnValue.mustache rename to hideout-mastodon/templates/returnValue.mustache diff --git a/hideout-core/templates/service.mustache b/hideout-mastodon/templates/service.mustache similarity index 100% rename from hideout-core/templates/service.mustache rename to hideout-mastodon/templates/service.mustache diff --git a/hideout-core/templates/serviceImpl.mustache b/hideout-mastodon/templates/serviceImpl.mustache similarity index 100% rename from hideout-core/templates/serviceImpl.mustache rename to hideout-mastodon/templates/serviceImpl.mustache diff --git a/hideout-core/templates/springdocDocumentationConfig.mustache b/hideout-mastodon/templates/springdocDocumentationConfig.mustache similarity index 100% rename from hideout-core/templates/springdocDocumentationConfig.mustache rename to hideout-mastodon/templates/springdocDocumentationConfig.mustache diff --git a/hideout-core/templates/springfoxDocumentationConfig.mustache b/hideout-mastodon/templates/springfoxDocumentationConfig.mustache similarity index 100% rename from hideout-core/templates/springfoxDocumentationConfig.mustache rename to hideout-mastodon/templates/springfoxDocumentationConfig.mustache diff --git a/hideout-core/templates/typeInfoAnnotation.mustache b/hideout-mastodon/templates/typeInfoAnnotation.mustache similarity index 100% rename from hideout-core/templates/typeInfoAnnotation.mustache rename to hideout-mastodon/templates/typeInfoAnnotation.mustache diff --git a/hideout-worker/build.gradle.kts b/hideout-worker/build.gradle.kts deleted file mode 100644 index db74eb66..00000000 --- a/hideout-worker/build.gradle.kts +++ /dev/null @@ -1,68 +0,0 @@ -plugins { - alias(libs.plugins.kotlin.jvm) - alias(libs.plugins.kotlin.spring) - alias(libs.plugins.spring.boot) -} - -apply { - plugin("io.spring.dependency-management") -} - -group = "dev.usbharu" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() - maven { - url = uri("https://git.usbharu.dev/api/packages/usbharu/maven") - } - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/usbharu/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") - } - } -} - -dependencies { - testImplementation(kotlin("test")) - implementation("dev.usbharu:owl-consumer:0.0.1") - implementation("dev.usbharu:owl-common:0.0.1") - implementation("dev.usbharu:owl-common-serialize-jackson:0.0.1") - implementation("dev.usbharu:hideout-core:0.0.1") - implementation("dev.usbharu:http-signature:1.0.0") - implementation("org.springframework.boot:spring-boot-starter") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.springframework.boot:spring-boot-starter-log4j2") - implementation(libs.jackson.databind) - implementation(libs.jackson.module.kotlin) - implementation(libs.bundles.coroutines) - - testImplementation("org.springframework.boot:spring-boot-starter-test") -} - -configurations { - all { - exclude("org.springframework.boot", "spring-boot-starter-logging") - exclude("ch.qos.logback", "logback-classic") - } -} - -tasks.test { - useJUnitPlatform() -} -kotlin { - jvmToolchain(21) -} \ No newline at end of file diff --git a/hideout-worker/gradle/wrapper/gradle-wrapper.jar b/hideout-worker/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e6441136..00000000 Binary files a/hideout-worker/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/HideoutWorker.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/HideoutWorker.kt deleted file mode 100644 index 3ef74a43..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/HideoutWorker.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 - -@SpringBootApplication -@ConfigurationPropertiesScan -class HideoutWorker - -fun main(args: Array) { - runApplication(*args) -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/SpringTaskRunnerLoader.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/SpringTaskRunnerLoader.kt deleted file mode 100644 index 6ff0b4a3..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/SpringTaskRunnerLoader.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 dev.usbharu.owl.consumer.TaskRunner -import dev.usbharu.owl.consumer.TaskRunnerLoader -import org.springframework.stereotype.Component - -@Component -class SpringTaskRunnerLoader(private val taskRunners: List) : TaskRunnerLoader { - override fun load(): Map = taskRunners.associateBy { it.name } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/WorkerRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/WorkerRunner.kt deleted file mode 100644 index 47f0b98e..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/WorkerRunner.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.worker.SpringConsumerConfig -import dev.usbharu.owl.common.property.* -import dev.usbharu.owl.consumer.StandaloneConsumer -import dev.usbharu.owl.consumer.StandaloneConsumerConfig -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.ApplicationArguments -import org.springframework.boot.ApplicationRunner -import org.springframework.stereotype.Component - -@Component -class WorkerRunner( - private val springTaskRunnerLoader: SpringTaskRunnerLoader, - @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val springCConsumerConfig: SpringConsumerConfig, -) : ApplicationRunner { - override fun run(args: ApplicationArguments?) { - GlobalScope.launch(Dispatchers.Default) { - val consumer = StandaloneConsumer( - taskRunnerLoader = springTaskRunnerLoader, - propertySerializerFactory = CustomPropertySerializerFactory( - setOf( - IntegerPropertySerializer(), - StringPropertyValueSerializer(), - DoublePropertySerializer(), - BooleanPropertySerializer(), - LongPropertySerializer(), - FloatPropertySerializer(), - ObjectPropertySerializer(objectMapper), - ) - ), - config = StandaloneConsumerConfig( - springCConsumerConfig.address, - springCConsumerConfig.port, - springCConsumerConfig.name, - springCConsumerConfig.hostname, - springCConsumerConfig.concurrency - ) - ) - consumer.init() - consumer.start() - } - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt deleted file mode 100644 index b557e259..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverAcceptTask -import dev.usbharu.hideout.core.external.job.DeliverAcceptTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverAcceptTaskRunner( - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, - private val transaction: Transaction, -) : AbstractTaskRunner(DeliverAcceptTaskDef) { - override suspend fun typedRun(typedParam: DeliverAcceptTask, taskRequest: TaskRequest): TaskResult { - - transaction.transaction { - apRequestService.apPost( - typedParam.inbox, - typedParam.accept, - actorRepository.findById(typedParam.signer) - ) - } - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt deleted file mode 100644 index 7780d00d..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverCreateTask -import dev.usbharu.hideout.core.external.job.DeliverCreateTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverCreateTaskRunner( - private val transaction: Transaction, - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, -) : AbstractTaskRunner(DeliverCreateTaskDef) { - override suspend fun typedRun(typedParam: DeliverCreateTask, taskRequest: TaskRequest): TaskResult { - transaction.transaction { - val signer = actorRepository.findByUrl(typedParam.actor) - - apRequestService.apPost(typedParam.inbox, typedParam.create, signer) - } - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt deleted file mode 100644 index 92961616..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverDeleteTask -import dev.usbharu.hideout.core.external.job.DeliverDeleteTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverDeleteTaskRunner( - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, -) : - AbstractTaskRunner(DeliverDeleteTaskDef) { - override suspend fun typedRun(typedParam: DeliverDeleteTask, taskRequest: TaskRequest): TaskResult { - apRequestService.apPost(typedParam.inbox, typedParam.delete, actorRepository.findById(typedParam.signer)) - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt deleted file mode 100644 index 6408a73c..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverReactionTask -import dev.usbharu.hideout.core.external.job.DeliverReactionTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverReactionTaskRunner( - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, -) : AbstractTaskRunner(DeliverReactionTaskDef) { - override suspend fun typedRun(typedParam: DeliverReactionTask, taskRequest: TaskRequest): TaskResult { - val signer = actorRepository.findByUrl(typedParam.actor) - - apRequestService.apPost( - typedParam.inbox, - typedParam.like, - signer - ) - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt deleted file mode 100644 index 7558197b..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverRejectTask -import dev.usbharu.hideout.core.external.job.DeliverRejectTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverRejectTaskRunner( - private val transaction: Transaction, - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, -) : AbstractTaskRunner(DeliverRejectTaskDef) { - override suspend fun typedRun(typedParam: DeliverRejectTask, taskRequest: TaskRequest): TaskResult { - val signer = transaction.transaction { - actorRepository.findById(typedParam.signer) - } - apRequestService.apPost(typedParam.inbox, typedParam.reject, signer) - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt deleted file mode 100644 index c811d2a6..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.DeliverUndoTask -import dev.usbharu.hideout.core.external.job.DeliverUndoTaskDef -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class DeliverUndoTaskRunner( - private val transaction: Transaction, - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, -) : AbstractTaskRunner(DeliverUndoTaskDef) { - override suspend fun typedRun(typedParam: DeliverUndoTask, taskRequest: TaskRequest): TaskResult { - val signer = transaction.transaction { - actorRepository.findById(typedParam.signer) - } - apRequestService.apPost(typedParam.inbox, typedParam.undo, signer) - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/InboxTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/InboxTaskRunner.kt deleted file mode 100644 index 1a5be9a4..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/InboxTaskRunner.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.worker - -import com.fasterxml.jackson.core.JsonParseException -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.external.job.InboxTask -import dev.usbharu.hideout.core.external.job.InboxTaskDef -import dev.usbharu.hideout.util.RsaUtil -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.common.PublicKey -import dev.usbharu.httpsignature.verify.HttpSignatureVerifier -import dev.usbharu.httpsignature.verify.Signature -import dev.usbharu.httpsignature.verify.SignatureHeaderParser -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Component - -@Component -class InboxTaskRunner( - private val activityPubProcessorList: List>, - private val signatureHeaderParser: SignatureHeaderParser, - private val signatureVerifier: HttpSignatureVerifier, - private val apUserService: APUserService, - private val objectMapper: ObjectMapper, - private val transaction: Transaction, -) : AbstractTaskRunner(InboxTaskDef) { - - @Value("\${hideout.debug.trace-inbox:false}") - private var traceJson: Boolean = false - - override suspend fun typedRun(typedParam: InboxTask, taskRequest: TaskRequest): TaskResult { - val jsonNode = objectMapper.readTree(typedParam.json) - - logger.info("START Process inbox. type: {}", typedParam.type) - if (traceJson) { - logger.trace("type: {}\njson: \n{}", typedParam.type, jsonNode.toPrettyString()) - } - - val map = typedParam.headers - - val httpRequest = typedParam.httpRequest.copy(headers = HttpHeaders(map)) - - logger.trace("Request: {}\nheaders: {}", httpRequest, map) - - val signature = parseSignatureHeader(httpRequest.headers) - - logger.debug("Has signature? {}", signature != null) - - // todo 不正なactorを取得してしまわないようにする - val verify = - signature?.let { - verifyHttpSignature( - httpRequest, - it, - transaction, - jsonNode.get("actor")?.asText() ?: signature.keyId - ) - } - ?: false - - logger.debug("Is verifying success? {}", verify) - - val activityPubProcessor = - activityPubProcessorList.firstOrNull { it.isSupported(typedParam.type) } as? ActivityPubProcessor - - if (activityPubProcessor == null) { - logger.warn("ActivityType {} is not support.", typedParam.type) - throw IllegalStateException("ActivityPubProcessor not found. type: ${typedParam.type}") - } - - val value = try { - objectMapper.treeToValue(jsonNode, activityPubProcessor.type()) - } catch (e: JsonParseException) { - logger.warn("Invalid JSON\n\n{}\n\n", jsonNode.toPrettyString()) - throw e - } - activityPubProcessor.process(ActivityPubProcessContext(value, jsonNode, httpRequest, signature, verify)) - - logger.info("SUCCESS Process inbox. type: {}", typedParam.type) - - - return TaskResult.ok() - } - - private suspend fun verifyHttpSignature( - httpRequest: HttpRequest, - signature: Signature, - transaction: Transaction, - actor: String, - ): Boolean { - val requiredHeaders = when (httpRequest.method) { - HttpMethod.GET -> getRequiredHeaders - HttpMethod.POST -> postRequiredHeaders - } - if (signature.headers.containsAll(requiredHeaders).not()) { - logger.warn("FAILED Invalid signature. require: {}", requiredHeaders) - return false - } - - val user = transaction.transaction { - apUserService.fetchPersonWithEntity(actor).second - } - - @Suppress("TooGenericExceptionCaught") - val verify = try { - signatureVerifier.verify( - httpRequest, - PublicKey(RsaUtil.decodeRsaPublicKeyPem(user.publicKey), signature.keyId) - ) - } catch (e: Exception) { - logger.warn("FAILED Verify Http Signature", e) - return false - } - - return verify.success - } - - @Suppress("TooGenericExceptionCaught") - private fun parseSignatureHeader(httpHeaders: HttpHeaders): Signature? { - return try { - println("Signature Header =" + httpHeaders.get("Signature").single()) - signatureHeaderParser.parse(httpHeaders) - } catch (e: RuntimeException) { - logger.trace("FAILED parse signature header", e) - null - } - } - - companion object { - private val logger = LoggerFactory.getLogger(InboxTaskRunner::class.java) - private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") - private val getRequiredHeaders = listOf("(request-target)", "date", "host") - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt deleted file mode 100644 index 458f4f4c..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.external.job.ReceiveFollowTask -import dev.usbharu.hideout.core.external.job.ReceiveFollowTaskDef -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class ReceiveFollowTaskRunner( - private val transaction: Transaction, - private val apUserService: APUserService, - private val actorRepository: ActorRepository, - private val relationshipService: RelationshipService, -) : AbstractTaskRunner(ReceiveFollowTaskDef) { - override suspend fun typedRun(typedParam: ReceiveFollowTask, taskRequest: TaskRequest): TaskResult { - - transaction.transaction { - - apUserService.fetchPerson(typedParam.actor, typedParam.targetActor) - val targetEntity = actorRepository.findByUrl(typedParam.targetActor) ?: throw UserNotFoundException.withUrl( - typedParam.targetActor - ) - val followActorEntity = actorRepository.findByUrl(typedParam.follow.actor) - ?: throw UserNotFoundException.withUrl(typedParam.follow.actor) - relationshipService.followRequest(followActorEntity.id, targetEntity.id) - } - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/SpringConsumerConfig.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/SpringConsumerConfig.kt deleted file mode 100644 index 2fa99d6b..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/SpringConsumerConfig.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.worker - -import org.springframework.boot.context.properties.ConfigurationProperties - -@ConfigurationProperties("hideout.worker") -data class SpringConsumerConfig( - val address: String = "localhost", - val port: Int = 50051, - val name: String = "hideout-worker", - val hostname: String = "localhost", - val concurrency: Int = 10, -) diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt deleted file mode 100644 index f25b8dce..00000000 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.worker - -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.external.job.UpdateActorTask -import dev.usbharu.hideout.core.external.job.UpdateActorTaskDef -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.owl.consumer.AbstractTaskRunner -import dev.usbharu.owl.consumer.TaskRequest -import dev.usbharu.owl.consumer.TaskResult -import org.springframework.stereotype.Component - -@Component -class UpdateActorWorker( - private val transaction: Transaction, - private val apUserService: APUserService, - private val postService: PostService, -) : AbstractTaskRunner(UpdateActorTaskDef) { - override suspend fun typedRun(typedParam: UpdateActorTask, taskRequest: TaskRequest): TaskResult { - transaction.transaction { - apUserService.fetchPerson(typedParam.apId, idOverride = typedParam.id) - - postService.restoreByRemoteActor(typedParam.id) - } - - return TaskResult.ok() - } -} \ No newline at end of file diff --git a/libs.versions.toml b/libs.versions.toml index c51feeb2..ecb026af 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -82,6 +82,7 @@ imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version = "3 thumbnailator = { module = "net.coobird:thumbnailator", version = "0.4.20" } flyway-core = { module = "org.flywaydb:flyway-core" } +flyway-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version = "10.14.0" } h2db = { module = "com.h2database:h2", version = "2.2.224" } @@ -109,5 +110,5 @@ spring-boot = { id = "org.springframework.boot", version = "3.3.0" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.8.1" } -openapi-generator = { id = "org.openapi.generator", version = "7.4.0" } +openapi-generator = { id = "org.openapi.generator", version = "7.6.0" } license-report = { id = "com.github.jk1.dependency-license-report", version = "2.8" } \ No newline at end of file diff --git a/owl/gradle.properties b/owl/gradle.properties index e981646f..1108ef87 100644 --- a/owl/gradle.properties +++ b/owl/gradle.properties @@ -2,4 +2,6 @@ kotlin.code.style=official org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureondemand=true -#ksp.useKSP2=true \ No newline at end of file +#ksp.useKSP2=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=warn \ No newline at end of file diff --git a/owl/owl-common/owl-common-serialize-jackson/build.gradle.kts b/owl/owl-common/owl-common-serialize-jackson/build.gradle.kts index 5eed5b43..bb09a5e4 100644 --- a/owl/owl-common/owl-common-serialize-jackson/build.gradle.kts +++ b/owl/owl-common/owl-common-serialize-jackson/build.gradle.kts @@ -19,5 +19,5 @@ tasks.test { useJUnitPlatform() } kotlin { - jvmToolchain(21) + jvmToolchain(17) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index c2d9aa85..8c6faaff 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,7 +20,7 @@ plugins { rootProject.name = "hideout" includeBuild("hideout-core") -includeBuild("hideout-worker") +includeBuild("hideout-mastodon") dependencyResolutionManagement { repositories {