diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index f740bfc..0000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -delta-force-guide-server.iml -build/ -!.gitlab-ci.yml -!build/libs/*.jar -.idea/ -.gradle \ No newline at end of file diff --git a/.gitea/workflows/build-and-deploy.yml b/.gitea/workflows/build-and-deploy.yml deleted file mode 100644 index caed485..0000000 --- a/.gitea/workflows/build-and-deploy.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Build and Deploy - -on: - release: - types: [published] - -env: - APP_NAME: delta-force-guide-server - -jobs: - build-and-release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 (Corretto) - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: corretto - cache: gradle - - - name: Build with Gradle - run: > - ./gradlew bootJar -x test - -PartefactVersion="${{ gitea.event.release.tag_name }}" - -PbuildChannel=stable - -Pvendor=${{ vars.VENDOR }} - - - name: Resolve JAR file path - id: jar - run: | - JAR_PATH=$(find build/libs -name '*.jar' | head -1) - echo "file=$JAR_PATH" >> "$GITHUB_OUTPUT" - - - name: Upload JAR to Gitea Release - run: | - TAG="${{ gitea.event.release.tag_name }}" - FILE="${{ steps.jar.outputs.file }}" - ASSET_NAME="${APP_NAME}-${TAG}.jar" - curl -X POST \ - -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ - -H "Content-Type: multipart/form-data" \ - -F "attachment=@${FILE};filename=${ASSET_NAME}" \ - "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${{ gitea.event.release.id }}/assets?name=${ASSET_NAME}" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ vars.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Generate image tags - id: meta - run: | - DOCKERHUB_USER="${{ vars.DOCKER_HUB_USERNAME }}" - echo "tag_version=${DOCKERHUB_USER}/${APP_NAME}:${{ gitea.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - echo "tag_latest=${DOCKERHUB_USER}/${APP_NAME}:latest" >> "$GITHUB_OUTPUT" - - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile.ci - build-args: | - JAR_FILE=${{ steps.jar.outputs.file }} - IMAGE_VERSION=${{ gitea.event.release.tag_name }} - IMAGE_VENDOR=${{ vars.VENDOR }} - BUILD_DATE=${{ gitea.event.release.published_at }} - push: true - tags: | - ${{ steps.meta.outputs.tag_version }} - ${{ steps.meta.outputs.tag_latest }} - - - name: Push README to Docker Hub - uses: christian-korneck/update-container-description-action@v1 - env: - DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME }} - DOCKER_PASS: ${{ secrets.DOCKER_HUB_TOKEN }} - with: - destination_container_repo: ${{ vars.DOCKER_HUB_USERNAME }}/${{ env.APP_NAME }} - provider: dockerhub - readme_file: README.md diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8f5999c..0000000 --- a/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM amazoncorretto:21 AS builder -WORKDIR /app -ADD . . -RUN chmod +x ./gradlew && \ - ./gradlew build -x test - -FROM amazoncorretto:21-alpine AS runner -WORKDIR /app -COPY --from=builder /app/build/libs/delta-force-guide-server-1.0.0.jar app.jar -ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/Dockerfile.ci b/Dockerfile.ci deleted file mode 100644 index 7925330..0000000 --- a/Dockerfile.ci +++ /dev/null @@ -1,19 +0,0 @@ -FROM amazoncorretto:21-alpine -WORKDIR /app - -ARG JAR_FILE -ARG IMAGE_VERSION=dev -ARG IMAGE_VENDOR=OnixByte -ARG BUILD_DATE - -COPY ${JAR_FILE} app.jar - -LABEL org.opencontainers.image.title="delta-force-guide-server" \ - org.opencontainers.image.description="REST API backend for managing Delta Force game firearm builds and modifications" \ - org.opencontainers.image.version="${IMAGE_VERSION}" \ - org.opencontainers.image.vendor="${IMAGE_VENDOR}" \ - org.opencontainers.image.source="https://git.onixbyte.com/onixbyte/delta-force-guide-server" \ - org.opencontainers.image.licenses="MIT" \ - org.opencontainers.image.created="${BUILD_DATE}" - -ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index a98947a..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,78 +0,0 @@ -plugins { - java - alias(libs.plugins.spring.boot) - alias(libs.plugins.spring.dependency.management) -} - -val artefactVersion: String by project -val buildChannel: String by project -val vendor: String by project - -group = "com.onixbyte.helix" -version = artefactVersion - -tasks.withType { - options.encoding = "UTF-8" -} - -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - -dependencies { - implementation(libs.jspecify.core) - implementation(platform(libs.aws.sdk.bom)) - implementation(libs.aws.sdk.s3) - implementation(libs.commons.io) - implementation(libs.commons.lang) - implementation(libs.commons.collections) - implementation(libs.hypersistence.core) - implementation(platform(libs.onixbyte.versionCatalogue)) - implementation(libs.onixbyte.tuple) - implementation(libs.onixbyte.commonToolbox) - implementation(libs.onixbyte.mathToolbox) - implementation(libs.onixbyte.identityGenerator) - implementation(libs.onixbyte.captcha) - implementation(libs.onixbyte.regions) - implementation(libs.onixbyte.cryptoToolbox) - implementation(libs.jwt.core) - implementation(libs.spring.boot.configurationProcessor) - implementation(libs.spring.boot.actuator) - implementation(libs.spring.boot.starter.web) - implementation(libs.spring.boot.starter.webFlux) - implementation(libs.spring.boot.starter.validation) - implementation(libs.spring.boot.starter.redis) - implementation(libs.spring.boot.starter.cache) - implementation(libs.spring.boot.starter.jpa) - implementation(libs.mybatis.starter.core) - implementation(libs.flyway.core) - implementation(libs.flyway.postgresql) - implementation(libs.jackson.jsr310) - implementation(libs.spring.boot.starter.doc) - implementation(libs.spring.boot.starter.security) - testImplementation(libs.spring.boot.starter.test) - testImplementation(libs.reactor.test) - testImplementation(libs.mybatis.starter.test) - testImplementation(libs.spring.security.test) - runtimeOnly(libs.postgres.driver) - testRuntimeOnly(libs.h2.database) - testRuntimeOnly(libs.junit.launcher) -} - -tasks.processResources { - filesMatching("application.yaml") { - expand( - "appVersion" to artefactVersion, - "channel" to buildChannel, - "vendor" to vendor - ) - } -} - -tasks.test { - useJUnitPlatform() -} diff --git a/config/application-prod.yaml.example b/config/application-prod.yaml.example deleted file mode 100644 index e68c145..0000000 --- a/config/application-prod.yaml.example +++ /dev/null @@ -1,57 +0,0 @@ -spring: - datasource: - url: jdbc:postgresql://localhost:5432/dfguide_dev - username: postgres - password: 123456 - driver-class-name: org.postgresql.Driver - data: - redis: - host: localhost - port: 6379 - database: 0 - # password: qwerty # Uncomment if password is necessary - -logging: - pattern: - # dateformat: dd MMM yyyy HH:mm:ss.SSS # Modify this for custom date format. - -app: - cookie: - http-only: true - secure: false - same-site: none - path: '/' - max-age: P1D - cors: - allowed-origins: # Cross-origin allowed origins - - "http://localhost:5173" # Dev server for vite. - - "http://localhost:4173" # Preview server for vite. - allow-credentials: true # Must be set to `true` since we are using cookie. You can change it only when you have modified how this application executing authentication. - allow-private-network: false - allowed-headers: - - "Content-Type" - - "Authorization" - allowed-methods: - - GET - - POST - - PUT - - PATCH - - DELETE - exposed-headers: - - "Content-Type" - - "Authorization" - max-age: PT2H - jwt: - issuer: dfguide.local # Issuer host - secret: qwertyuiopasdfghjklzxcvbnm123456 # JWT singing secret, a 32-byte long or longer string is recommended - valid-time: PT2H # JWT valid duration - webhook: - github: - secret: 123456 - allowed-users: [ octotcat, onixbyte ] - -springdoc: - api-docs: - enabled: true # Set to `false` if you do not need api docs (recommended in production mode). - swagger-ui: - enabled: true # Set to `false` if you do not need swagger ui (recommended in production mode). \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index d9650f6..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,78 +0,0 @@ -[versions] -jspecifyVersion = "1.0.0" -javaJwtVersion = "4.5.1" -postgresDriverVersion = "42.7.9" -h2Version = "2.2.224" -springBootVersion = "3.5.13" -reactorVersion = "3.7.8" -junitPlatformVersion = "1.12.2" -onixbyteVersion = "3.4.0" -onixbyteCaptcha = "1.1.0" -onixbyteRegions = "2025.12.0" -awsSdkVersion = "2.25.48" -commonsIoVersion = "2.16.1" -commonsCollections = "4.5.0" -commonsLangVersion = "3.20.0" -mybatisVersion = "3.0.5" -jacksonVersion = "2.19.2" -hypersistenceVersion = "3.14.0" -springDependencyManagementVersion = "1.1.7" -springDocVersion = "2.8.16" - -[libraries] -# General Utilities -jspecify-core = { group = "org.jspecify", name = "jspecify", version.ref = "jspecifyVersion" } -commons-io = { group = "commons-io", name = "commons-io", version.ref = "commonsIoVersion" } -commons-collections = { group = "org.apache.commons", name = "commons-collections4", version.ref = "commonsCollections" } -commons-lang = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commonsLangVersion" } -jackson-jsr310 = { group = "com.fasterxml.jackson.datatype", name = "jackson-datatype-jsr310", version.ref = "jacksonVersion" } - -# Onixbyte Ecosystem -onixbyte-versionCatalogue = { group = "com.onixbyte", name = "version-catalogue", version.ref = "onixbyteVersion" } -onixbyte-tuple = { group = "com.onixbyte", name = "tuple" } -onixbyte-commonToolbox = { group = "com.onixbyte", name = "common-toolbox" } -onixbyte-cryptoToolbox = { group = "com.onixbyte", name = "crypto-toolbox", version.ref = "onixbyteVersion" } -onixbyte-mathToolbox = { group = "com.onixbyte", name = "math-toolbox" } -onixbyte-identityGenerator = { group = "com.onixbyte", name = "identity-generator" } -onixbyte-captcha = { group = "com.onixbyte", name = "captcha", version.ref = "onixbyteCaptcha" } -onixbyte-regions = { group = "com.onixbyte", name = "regions4j", version.ref = "onixbyteRegions" } - -# Persistence & Database -mybatis-starter-core = { group = "org.mybatis.spring.boot", name = "mybatis-spring-boot-starter", version.ref = "mybatisVersion" } -spring-boot-starter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" } -hypersistence-core = { group = "io.hypersistence", name = "hypersistence-utils-hibernate-63", version.ref = "hypersistenceVersion" } -postgres-driver = { group = "org.postgresql", name = "postgresql", version.ref = "postgresDriverVersion" } -h2-database = { group = "com.h2database", name = "h2", version.ref = "h2Version" } -spring-boot-starter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis", version.ref = "springBootVersion" } -flyway-core = { group = "org.flywaydb", name = "flyway-core" } -flyway-postgresql = { group = "org.flywaydb", name = "flyway-database-postgresql" } - -# Spring Boot Core & Web -spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" } -spring-boot-starter-webFlux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux" } -spring-boot-starter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation" } -spring-boot-starter-cache = { group = "org.springframework.boot", name = "spring-boot-starter-cache" } -spring-boot-configurationProcessor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor" } -spring-boot-actuator = { group = "org.springframework.boot", name = "spring-boot-starter-actuator" } - -# Security & Auth -spring-boot-starter-security = { group = "org.springframework.boot", name = "spring-boot-starter-security" } -jwt-core = { group = "com.auth0", name = "java-jwt", version.ref = "javaJwtVersion" } - -# Spring Doc -spring-boot-starter-doc = { group = "org.springdoc", name = "springdoc-openapi-starter-webmvc-ui", version.ref = "springDocVersion" } - -# Cloud Services -aws-sdk-bom = { group = "software.amazon.awssdk", name = "bom", version.ref = "awsSdkVersion" } -aws-sdk-s3 = { group = "software.amazon.awssdk", name = "s3" } - -# Testing -spring-boot-starter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test", version.ref = "springBootVersion" } -spring-security-test = { group = "org.springframework.security", name = "spring-security-test" } -reactor-test = { group = "io.projectreactor", name = "reactor-test", version.ref = "reactorVersion" } -junit-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junitPlatformVersion" } -mybatis-starter-test = { group = "org.mybatis.spring.boot", name = "mybatis-spring-boot-starter-test", version.ref = "mybatisVersion" } - -[plugins] -spring-boot = { id = "org.springframework.boot", version.ref = "springBootVersion" } -spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "springDependencyManagementVersion" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index aaaabb3..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 23d15a9..0000000 --- a/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# 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/HEAD/platforms/jvm/plugins-application/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 - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# 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="\\\"\\\"" - - -# 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 - 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. - -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 -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 - - -# 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, 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. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# 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. -# -# 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/gradlew.bat b/gradlew.bat deleted file mode 100644 index db3a6ac..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@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 -@rem SPDX-License-Identifier: Apache-2.0 -@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= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -: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 diff --git a/server/.gitattributes b/server/.gitattributes deleted file mode 100644 index 8af972c..0000000 --- a/server/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/gradlew text eol=lf -*.bat text eol=crlf -*.jar binary diff --git a/server/.gitea/workflows/build-and-deploy.yml b/server/.gitea/workflows/build-and-deploy.yml deleted file mode 100644 index caed485..0000000 --- a/server/.gitea/workflows/build-and-deploy.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Build and Deploy - -on: - release: - types: [published] - -env: - APP_NAME: delta-force-guide-server - -jobs: - build-and-release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 (Corretto) - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: corretto - cache: gradle - - - name: Build with Gradle - run: > - ./gradlew bootJar -x test - -PartefactVersion="${{ gitea.event.release.tag_name }}" - -PbuildChannel=stable - -Pvendor=${{ vars.VENDOR }} - - - name: Resolve JAR file path - id: jar - run: | - JAR_PATH=$(find build/libs -name '*.jar' | head -1) - echo "file=$JAR_PATH" >> "$GITHUB_OUTPUT" - - - name: Upload JAR to Gitea Release - run: | - TAG="${{ gitea.event.release.tag_name }}" - FILE="${{ steps.jar.outputs.file }}" - ASSET_NAME="${APP_NAME}-${TAG}.jar" - curl -X POST \ - -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ - -H "Content-Type: multipart/form-data" \ - -F "attachment=@${FILE};filename=${ASSET_NAME}" \ - "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${{ gitea.event.release.id }}/assets?name=${ASSET_NAME}" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ vars.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Generate image tags - id: meta - run: | - DOCKERHUB_USER="${{ vars.DOCKER_HUB_USERNAME }}" - echo "tag_version=${DOCKERHUB_USER}/${APP_NAME}:${{ gitea.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - echo "tag_latest=${DOCKERHUB_USER}/${APP_NAME}:latest" >> "$GITHUB_OUTPUT" - - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile.ci - build-args: | - JAR_FILE=${{ steps.jar.outputs.file }} - IMAGE_VERSION=${{ gitea.event.release.tag_name }} - IMAGE_VENDOR=${{ vars.VENDOR }} - BUILD_DATE=${{ gitea.event.release.published_at }} - push: true - tags: | - ${{ steps.meta.outputs.tag_version }} - ${{ steps.meta.outputs.tag_latest }} - - - name: Push README to Docker Hub - uses: christian-korneck/update-container-description-action@v1 - env: - DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME }} - DOCKER_PASS: ${{ secrets.DOCKER_HUB_TOKEN }} - with: - destination_container_repo: ${{ vars.DOCKER_HUB_USERNAME }}/${{ env.APP_NAME }} - provider: dockerhub - readme_file: README.md diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index e026c93..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,162 +0,0 @@ -### macOS -# General -.DS_Store -.AppleDouble -.LSOverride - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Linux -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# Metadata left by Dolphin file manager, which comes with KDE Plasma -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -# Log files created by default by the nohup command -nohup.out - -### Windows - -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### JetBrains IDE -# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea - -# Gradle and Maven with auto-import -*.iml -*.ipr - -# File-based project format -*.iws - -# IntelliJ -out/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based HTTP Client -http-client.private.env.json - -### Server -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar -*.jar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -# Config -config/*.yml -config/*.yaml -config/*.properties - -# Tests -test/ - -### Gradle -.gradle -**/build/ -!**/src/**/build/ - -!gradle/ -!gradle/wrapper -!gradle/wrapper/gradle-wrapper.jar -!gradle/wrapper/gradle-wrapper.properties -gradle-app.setting -.gradletasknamecache - -### Claude Code -.claude/settings.local.json -.claude/memory/ -.claude/plans/ -.claude/worktrees/ -.claude/scheduled_tasks.json - -# Eclipse Gradle plugin generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -gradle.properties - -# frp config -frpc.toml diff --git a/server/CLAUDE.md b/server/CLAUDE.md deleted file mode 100644 index 3e567dd..0000000 --- a/server/CLAUDE.md +++ /dev/null @@ -1,109 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Coding Standards - -- **Style**: Follow the Google Java Coding Style as the foundation. -- **Indentation**: Use 4 spaces — no tabs. -- **Line length**: Maximum 100 characters per line. -- **Comments**: All code comments must use British English spelling (e.g. "colour" not "color", "behaviour" not "behavior", "serialise" not "serialize", "analyse" not "analyze", "traveller" not "traveler"). - -## Build & Test Commands - -```bash -# Build the project (skip tests) -./gradlew build -x test - -# Run all tests -./gradlew test - -# Run a single test class -./gradlew test --tests "com.onixbyte.deltaforceguide.service.PasswordEncoderTest" - -# Run a specific test method -./gradlew test --tests "com.onixbyte.deltaforceguide.service.PasswordEncoderTest.generatePassword" - -# Build the full JAR -./gradlew bootJar -``` - -The project uses Gradle with Java 21 (Amazon Corretto). Tests use JUnit 5 with the Spring Boot test framework, H2 in-memory database for test runtime, and Spring Security test support. Tests require an active `dev` profile. - -## Code Architecture - -**Delta Force Guide Server** — A REST API backend for managing Delta Force game firearm builds/modifications. - -### Package structure - -``` -com.onixbyte.deltaforceguide -├── client/ # External service clients (TokenClient for JWT) -├── config/ # Spring beans: Security, CORS, Cache/Redis, Jackson, MyBatis, Spring Data -├── controller/ # REST controllers (Firearm, Modification, Tag, Auth) -├── domain/ -│ ├── converter/ # JPA attribute converters (FirearmTypeConverter) -│ ├── dto/ # Request/response records (FirearmRequest, ModificationResponse, etc.) -│ └── entity/ # JPA entities (Firearm, Modification, User, Accessory, Tuning) -├── enumeration/ # Enums (FirearmType) -├── exeption/ # BizException (custom runtime exception with HTTP status) -├── filter/ # TokenAuthenticationFilter (JWT auth via OncePerRequestFilter) -├── manager/ # Thin @Transactional wrappers around repositories -├── mapper/ # MyBatis mappers (configured but currently unused) -├── properties/ # @ConfigurationProperties records (Cors, Token, Cookie) -├── repository/ # Spring Data JPA repositories -├── security/ -│ ├── authentication/ # Custom UsernamePasswordAuthentication impl -│ └── provider/ # UsernamePasswordAuthenticationProvider -├── service/ # Business logic layer (FirearmService, ModificationService, AuthService, etc.) -├── shared/ # Constants and utility classes (CookieName, CredentialProvider, JacksonModules) -└── utils/ # Helpers (DateTimeUtil) -``` - -### Key design decisions - -- **JPA + native queries**: Most CRUD uses Spring Data JPA. Native queries (in `ModificationRepository`) handle JSONB tag filtering with Postgres `@>` operator. -- **Custom auth flow**: JWT tokens in httpOnly cookies (`AccessToken`). Spring Security with a custom `UsernamePasswordAuthenticationProvider` and `TokenAuthenticationFilter`. Tokens are auto-renewed within 5 min of expiry. -- **JSONB storage**: `Modification.tags` and `Modification.accessories` (including nested `Tuning` objects) are stored as JSONB columns using Hypersistence Utils `JsonType`. -- **Strict layering**: The call chain must follow `Controller → Service → Manager → Repository/Mapper`. Skipping layers (e.g. Controller calling Manager directly, Service calling Repository directly) is not permitted. Each layer has a distinct responsibility: Controller handles HTTP concerns, Service contains business logic, Manager manages `@Transactional` boundaries and data access coordination, Repository/Mapper handles raw data access. -- **DTOs as Java records**: All request/response objects are immutable records with static `from()` factory methods for entity→DTO conversion. -- **Flyway migrations**: SQL migrations in `src/main/resources/db/migration/` — V2 (init), V3 (bullet/damage fields), V4 (user), V5 (accessories JSONB column). - -### Data model - -- `firearm` table: id, name, type (int→FirearmType enum), level, calibre, fire_rate, armour_damage, body_damage, review -- `modification` table: id, firearm_id (FK→firearm), name, code, tags (jsonb), accessories (jsonb), note, author, video_url -- `app_user` table: id, username, email -- `user_credential` table: user_id, provider, credential (hashed) - -### API endpoints - -| Path | Methods | Auth | -|-------------------------------|------------------|--------------------------------------| -| `/firearms` | GET, POST | GET public, POST requires auth | -| `/firearms/{id}` | GET, PUT, DELETE | GET public, PUT/DELETE requires auth | -| `/modifications` | GET, POST | GET public, POST requires auth | -| `/modifications/{id}` | GET, PUT, DELETE | GET public, PUT/DELETE requires auth | -| `/modifications/batch` | POST | Requires auth | -| `/modifications/batch-delete` | DELETE | Requires auth | -| `/tags` | GET | Public | -| `/auth/login` | POST | Public | -| `/auth/logout` | POST | Authenticated | - -### Commit convention - -Conventional commits: `feat:`, `chore:`, `fix:`. Messages are in English, present tense imperative style. - -### External dependencies - -- **DB**: PostgreSQL (via Flyway migrations), H2 in test -- **Cache**: Redis (via Spring Cache + RedisTemplate with GenericJackson2JsonRedisSerializer) -- **Auth**: java-jwt (auth0), BCrypt -- **Docs**: springdoc-openapi (Swagger UI) on dev profile -- **Onixbyte internal libs**: version-catalogue, tuple, common-toolbox, math-toolbox, identity-generator, captcha, regions -- **AWS**: S3 SDK - -### Profiles - -- `dev`: Enables Swagger UI, connects to dev DB/Redis at `dfguide.onixbyte.cn`. Config in `config/application-dev.yaml`. -- Default profile: Used for production, no Swagger, connects to production datasource. diff --git a/server/LICENCE b/server/LICENCE deleted file mode 100644 index 73c61fd..0000000 --- a/server/LICENCE +++ /dev/null @@ -1,21 +0,0 @@ -MIT Licence - -Copyright (c) 2026 OnixByte - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 13455ae..0000000 --- a/server/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# Delta Force Guide Server - -REST API backend for managing **Delta Force** game firearm builds and modifications. Provides endpoints to browse, create, and share weapon configurations including attachments, tuning setups, tags, and build reviews. - - -## Tech Stack - -| Layer | Technology | -|-----------|------------------------------------------------------------| -| Language | Java 21 (Amazon Corretto) | -| Framework | Spring Boot 3.x (Web, Security, Data JPA, Cache, Actuator) | -| Build | Gradle (Kotlin DSL) | -| Database | PostgreSQL (Flyway migrations, JSONB columns) | -| Cache | Redis (2-hour TTL) | -| Auth | Custom JWT via httpOnly cookies + BCrypt | -| API Docs | springdoc-openapi (Swagger UI, dev profile only) | -| Container | Multi-stage Docker image (Amazon Corretto 21 → Alpine) | -| CI/CD | GitHub Actions — builds on release publish | - - -## Quick Start - -### Prerequisites - -- JDK 21 (Amazon Corretto recommended) -- PostgreSQL -- Redis - -### Configure - -Copy and customise the development config: - -```bash -cp config/application-prod.yaml.example \ - config/application-dev.yaml -``` - -Fill in your datasource and Redis connection details, then place the production config at `config/`, then enable it by environment variable `SPRING_PROFILES_ACTIVE`. - -### Build - -```bash -# Compile (skip tests) -./gradlew build -x test - -# Run all tests -./gradlew test - -# Build executable JAR -./gradlew bootJar -``` - -### Run - -```bash -SPRING_PROFILES_ACTIVE=prod java -jar build/libs/delta-force-guide-server-$version.jar -``` - -Swagger UI is available at `http://localhost:8080/swagger-ui.html` when the `dev` profile is active. - -## Data Model - -- **Firearm** — weapon base stats (name, type, level, calibre, fire rate, armour/body damage, review) -- **Modification** — a build attached to a firearm, with name, code, tags (JSONB), accessories including nested tuning objects (JSONB), author notes, and video links -- **App User** — registered user (username, email) with hashed credentials via BCrypt - -Tags and accessories are stored as PostgreSQL JSONB columns using [Hypersistence Utils](https://github.com/vladmihalcea/hypersistence-utils), enabling flexible per-build metadata and filtering with the `@>` operator. - - -## Architecture - -``` -Controller → Service → Manager → Repository / Mapper - (HTTP) (logic) (@Transactional) (data access) -``` - -The call chain is strictly enforced — skipping layers is not permitted. All request/response objects are Java records with static `from()` factory methods for entity-to-DTO conversion. - - -## Docker - -```bash -# Build image -docker pull registry.onixbyte.cn/onixbyte/delta-force-guide-server:latest - -# Run container -docker run -p 8080:8080 \ - -v /path/to/config:/app/config \ - -e SPRING_PROFILES_ACTIVE=$your_active_profiles - delta-force-guide-server -``` - -Pre-built images are published to **Self-hosted GitLab Container Registry** (`registry.onixbyte.cn/onixbyte/delta-force-guide-server`) on every release. - - -## CI/CD - -GitLab CI triggers on **tags**. The pipeline: - -1. Builds the boot JAR with the release tag as the version -2. Uploads the JAR as a release asset -3. Builds and pushes a multi-arch Docker image to GHCR tagged with both `:latest` and `:` - -No tests are run in CI by design — tests are expected to pass locally before a release is cut. - - -## Profiles - -| Profile | Purpose | -|---------|-----------------------------------------------------------------------| -| `dev` | Enables Swagger UI, connects to dev DB/Redis at `dfguide.onixbyte.cn` | -| default | Production mode, no Swagger, uses production datasource | - - -## Project Structure - -``` -src/main/java/com/onixbyte/deltaforceguide/ -├── client/ External service HTTP clients -├── config/ Spring bean definitions (Security, CORS, Cache, Jackson, MyBatis) -├── controller/ REST controllers -├── domain/ -│ ├── converter/ JPA attribute converters -│ ├── dto/ Request/response records -│ └── entity/ JPA entities -├── enumeration/ Enums (FirearmType) -├── exception/ Custom BizException with HTTP status mapping -├── filter/ TokenAuthenticationFilter (JWT through OncePerRequestFilter) -├── manager/ @Transactional wrappers around repositories -├── mapper/ MyBatis mapper interfaces (reserved for future use) -├── properties/ @ConfigurationProperties records -├── repository/ Spring Data JPA repositories -├── security/ -│ ├── authentication/ Custom UsernamePasswordAuthentication -│ └── provider/ UsernamePasswordAuthenticationProvider -├── service/ Business logic -├── shared/ Constants and utility classes -└── utils/ General-purpose helpers -``` - - -## Versioning - -The application exposes its version at `/versions` (GET). - - -## Licence - -This project is licensed under the [MIT Licence](LICENCE). Copyright © 2026 OnixByte. - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java b/server/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java deleted file mode 100644 index a23fb88..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Entry point for the Delta Force Guide Server application. - * - * @author zihluwang - */ -@SpringBootApplication -public class DeltaForceGuideApplication { - - public static void main(String[] args) { - SpringApplication.run(DeltaForceGuideApplication.class, args); - } - -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java b/server/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java deleted file mode 100644 index a7ad3f1..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.onixbyte.deltaforceguide.client; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.properties.TokenProperties; -import com.onixbyte.deltaforceguide.utils.DateTimeUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * Client for generating and verifying JSON Web Tokens using the Auth0 java-jwt library. - * - * @author zihluwang - */ -@Component -public class TokenClient { - - private final Algorithm algorithm; - private final TokenProperties tokenProperties; - private final JWTVerifier verifier; - - /** - * Constructs a new TokenClient with the necessary algorithm and token properties. - * - * @param algorithm the signing algorithm used to secure the JWT - * @param tokenProperties the configuration properties for the token, such as issuer and - * validity period - */ - @Autowired - public TokenClient( - Algorithm algorithm, - TokenProperties tokenProperties, - JWTVerifier verifier - ) { - this.algorithm = algorithm; - this.tokenProperties = tokenProperties; - this.verifier = verifier; - } - - /** - * Generate a JSON Web Token to the current user. - * - * @param user the current user for whom the token is being generated - * @return a JWT string - */ - public String generateToken(User user) { - var issuedAt = LocalDateTime.now(); - var expiresAt = issuedAt.plus(tokenProperties.validTime()); - - return JWT.create() - .withSubject(user.getUsername()) - .withIssuer(tokenProperties.issuer()) - .withIssuedAt(DateTimeUtil.asInstant(issuedAt)) - .withExpiresAt(DateTimeUtil.asInstant(expiresAt)) - .sign(algorithm); - } - - /** - * Verify and decode token. - * - * @param token a JWT token - * @return information included in the given token - * @throws com.auth0.jwt.exceptions.JWTVerificationException if the token is invalid, such as - * expired, or not signed by - * specific server - */ - public DecodedJWT verifyToken(String token) { - return verifier.verify(token); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java deleted file mode 100644 index 6bba2db..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.interceptor.GitHubWebhookInterceptor; -import com.onixbyte.deltaforceguide.interceptor.TrafficInterceptor; -import com.onixbyte.deltaforceguide.properties.AppProperties; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@EnableConfigurationProperties(AppProperties.class) -public class AppConfig implements WebMvcConfigurer { - - private final TrafficInterceptor trafficInterceptor; - - private final GitHubWebhookInterceptor gitHubWebhookInterceptor; - - @Autowired - public AppConfig( - TrafficInterceptor trafficInterceptor, - GitHubWebhookInterceptor gitHubWebhookInterceptor - ) { - this.trafficInterceptor = trafficInterceptor; - this.gitHubWebhookInterceptor = gitHubWebhookInterceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(trafficInterceptor); - registry.addInterceptor(gitHubWebhookInterceptor) - .addPathPatterns("/webhooks/github"); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java deleted file mode 100644 index 0502c4a..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.shared.JacksonRedisSerialiser; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.RedisSerializer; - -import java.time.Duration; - -/** - * Configuration class for Redis-based caching components. - *

- * This configuration class provides beans for Redis cache management and template operations - * within the Helix application. It configures custom serialisation strategies using - * {@link GenericJackson2JsonRedisSerializer} for values and string serialisation for keys, - * ensuring optimal performance and compatibility with JSON-based data structures. - *

- * The configuration includes: - *

- * - * @author zihluwang - * @see RedisCacheManager - * @see RedisTemplate - * @see GenericJackson2JsonRedisSerializer - * @since 1.0.0 - */ -@Configuration -@EnableCaching -public class CacheConfig { - - /** - * Creates a custom Redis cache manager with JSON serialisation support. - *

- * This method configures a {@link RedisCacheManager} that uses string serialisation for cache - * keys and {@link GenericJackson2JsonRedisSerializer} for cache values. This setup ensures that - * complex objects can be stored and retrieved from Redis cache whilst maintaining readability - * and compatibility with JSON-based systems. - * - * @param connectionFactory the Redis connection factory used to establish connections - * @return a configured {@link RedisCacheManager} with custom serialisation settings - * @see RedisCacheManager - * @see GenericJackson2JsonRedisSerializer - * @see RedisSerializationContext - */ - @Bean - public RedisCacheManager cacheManager( - RedisConnectionFactory connectionFactory - ) { - var _keySerializer = RedisSerializer.string(); - - var cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() - .serializeKeysWith(RedisSerializationContext.SerializationPair - .fromSerializer(_keySerializer)) - .serializeValuesWith(RedisSerializationContext.SerializationPair - .fromSerializer(JacksonRedisSerialiser.INSTANCE)) - .entryTtl(Duration.ofMinutes(90L)); - - return RedisCacheManager.RedisCacheManagerBuilder - .fromConnectionFactory(connectionFactory) - .cacheDefaults(cacheConfiguration) - .build(); - } - - /** - * Creates a Redis template for direct Redis operations with custom serialisation. - *

- * This method configures a {@link RedisTemplate} that uses string serialisation for keys - * and {@link GenericJackson2JsonRedisSerializer} for values. This template provides low-level - * access to Redis operations whilst ensuring consistent serialisation strategies across - * the application. - *

- * The template is fully configured and ready for use after bean creation. - * - * @param connectionFactory the Redis connection factory used to establish connections - * @return a fully configured {@link RedisTemplate} for Redis operations - * @see RedisTemplate - * @see GenericJackson2JsonRedisSerializer - * @see RedisSerializer - */ - @Bean - public RedisTemplate redisTemplate( - RedisConnectionFactory connectionFactory - ) { - var redisTemplate = new RedisTemplate(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.setKeySerializer(RedisSerializer.string()); - redisTemplate.setValueSerializer(JacksonRedisSerialiser.INSTANCE); - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java deleted file mode 100644 index 1e13243..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.properties.CorsProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; -import java.util.stream.Stream; - -/** - * Configuration for CORS (Cross-Origin Resource Sharing) policies. - * - * @author zihluwang - */ -@Configuration -@EnableConfigurationProperties({CorsProperties.class}) -public class CorsConfig { - - /** - * Creates the CORS configuration source with properties from configuration. - * - * @param properties the CORS configuration properties - * @return the CORS configuration source - */ - @Bean - public CorsConfigurationSource corsConfigurationSource( - CorsProperties properties - ) { - var corsConfiguration = new CorsConfiguration(); - corsConfiguration.setAllowCredentials(properties.allowCredentials()); - corsConfiguration.setAllowedOrigins(List.of(properties.allowedOrigins())); - corsConfiguration.setAllowedHeaders(List.of(properties.allowedHeaders())); - corsConfiguration.setAllowedMethods(Stream.of(properties.allowedMethods()) - .map(HttpMethod::name) - .toList()); - corsConfiguration.setMaxAge(properties.maxAge()); - corsConfiguration.setAllowPrivateNetwork(properties.allowPrivateNetwork()); - corsConfiguration.setExposedHeaders(List.of(properties.exposedHeaders())); - - var corsConfigurationSource = new UrlBasedCorsConfigurationSource(); - corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); - return corsConfigurationSource; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java deleted file mode 100644 index 893852a..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.filter.WebhookFilter; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class FilterConfig { - - @Bean - public FilterRegistrationBean webhookFilterBean(WebhookFilter webhookFilter) { - var registrationBean = new FilterRegistrationBean(); - - registrationBean.setFilter(webhookFilter); - registrationBean.addUrlPatterns("/webhooks/*"); - registrationBean.setOrder(1); - - return registrationBean; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java deleted file mode 100644 index 895bcd1..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.onixbyte.deltaforceguide.shared.JacksonModules; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for Jackson JSON serialisation and deserialisation settings. - * - * @author zihluwang - */ -@Configuration -public class JacksonConfig { - - @Bean - public Jackson2ObjectMapperBuilderCustomizer jacksonCustomiser() { - return (builder) -> { - builder.modules(JacksonModules.DATE_TIME_MODULE); - builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - }; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java deleted file mode 100644 index 0e2ae74..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for MyBatis SQL mapping framework integration. - * - * @author zihluwang - */ -@Configuration -@MapperScan(basePackages = {"com.onixbyte.deltaforceguide.mapper"}) -public class MyBatisConfig { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java deleted file mode 100644 index 9f47fe1..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.info.Contact; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.info.License; -import org.springframework.context.annotation.Configuration; - -@OpenAPIDefinition( - info = @Info( - title = "Delta Force Guide Server", - description = "API for managing Delta Force game firearm builds", - version = "1.3.4", - contact = @Contact( - name = "Zihlu Wang", - email = "zihlu.wang@onixbyte.com" - ), - license = @License( - name = "MIT", - url = "https://git.onixbyte.cn/onixbyte/delta-force-guide-server/-/raw/main/LICENCE" - ) - ) -) -@Configuration -public class OpenApiConfiguration { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java deleted file mode 100644 index b32a691..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter; -import com.onixbyte.deltaforceguide.properties.CookieProperties; -import com.onixbyte.deltaforceguide.properties.TokenProperties; -import com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -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.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.web.cors.CorsConfigurationSource; - -/** - * Spring Security configuration defining authentication, authorisation, and filter chains. - * - * @author zihluwang - */ -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -@EnableConfigurationProperties({TokenProperties.class, CookieProperties.class}) -public class SecurityConfig { - - /** - * Configures the HTTP security filter chain including endpoint authorisation and JWT filter. - * - * @param http the HTTP security builder - * @return the configured security filter chain - */ - @Bean - public SecurityFilterChain securityFilterChain( - HttpSecurity httpSecurity, - CorsConfigurationSource corsConfigurationSource, - TokenAuthenticationFilter tokenAuthenticationFilter - ) throws Exception { - return httpSecurity - .cors((cors) -> cors - .configurationSource(corsConfigurationSource)) - .csrf(AbstractHttpConfigurer::disable) - .sessionManagement((customiser) -> customiser - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests((customiser) -> customiser - .anyRequest().permitAll() - ) - .addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class) - .build(); - } - - /** - * Provides the BCrypt password encoder for credential hashing. - * @return the BCrypt password encoder - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** - * Provides the authentication manager for the security configuration. - * - * @return the authentication manager - */ - @Bean - public AuthenticationManager authenticationManager( - UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider - ) { - return new ProviderManager( - usernamePasswordAuthenticationProvider - ); - } - - @Bean - public Algorithm algorithm(TokenProperties properties) { - return Algorithm.HMAC256(properties.secret()); - } - - @Bean - public JWTVerifier verifier(Algorithm algorithm, TokenProperties tokenProperties) { - return JWT.require(algorithm) - .withIssuer(tokenProperties.issuer()) - .build(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java deleted file mode 100644 index 1dfe3ff..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -/** - * Configuration for Spring Data JPA auditing and repository settings. - * - * @author zihluwang - */ -@Configuration -@EnableJpaRepositories(basePackages = {"com.onixbyte.deltaforceguide.repository"}) -public class SpringDataConfig { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java b/server/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java deleted file mode 100644 index d9c5db7..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.properties.GitHubWebhookProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({GitHubWebhookProperties.class}) -public class WebhookConfig { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java deleted file mode 100644 index db0f42e..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.LoginRequest; -import com.onixbyte.deltaforceguide.domain.dto.UserResponse; -import com.onixbyte.deltaforceguide.client.TokenClient; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.AuthService; -import com.onixbyte.deltaforceguide.service.CookieService; -import com.onixbyte.deltaforceguide.shared.CookieName; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.time.Duration; -import java.time.LocalDateTime; - -/** - * REST controller for user authentication endpoints (login, logout). - * - * @author zihluwang - */ -@Tag(name = "用户鉴权", description = "处理用户登录与退出功能") -@RestController -@RequestMapping("/auth") -public class AuthController { - - private final AuthService authService; - private final TokenClient tokenClient; - private final CookieService cookieService; - - public AuthController(AuthService authService, TokenClient tokenClient, CookieService cookieService) { - this.authService = authService; - this.tokenClient = tokenClient; - this.cookieService = cookieService; - } - - @Operation(description = "用户登录") - @PostMapping("/login") - public ResponseEntity login(@Validated @RequestBody LoginRequest request) { - var user = authService.login(request); - var currentTime = LocalDateTime.now(); - var accessToken = tokenClient.generateToken(user); - var accessTokenCookie = cookieService.buildCookie(CookieName.ACCESS_TOKEN, accessToken); - var cookieMaxAge = accessTokenCookie.getMaxAge(); - - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()) - .body(UserResponse.from(user, currentTime.plus(cookieMaxAge))); - } - - @RequiresAuth - @Operation(description = "退出登录") - @PostMapping("/logout") - public ResponseEntity logout() { - var expiredCookie = cookieService.buildCookie(CookieName.ACCESS_TOKEN, "", Duration.ZERO); - return ResponseEntity.noContent() - .header(HttpHeaders.SET_COOKIE, expiredCookie.toString()) - .build(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java deleted file mode 100644 index 527e730..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.service.DailyPasswordService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * REST controller for retrieving daily-generated passwords. - * - * @author zihluwang - */ -@Tag(name = "每日密码", description = "获取每日密码信息") -@RestController -@RequestMapping("/daily-passwords") -public class DailyPasswordController { - - private final DailyPasswordService dailyPasswordService; - - public DailyPasswordController(DailyPasswordService dailyPasswordService) { - this.dailyPasswordService = dailyPasswordService; - } - - @Operation(description = "获取当日的每日密码数据,该数据将被缓存一天") - @GetMapping - public DailyPasswordResponse getDailyPassword() { - return dailyPasswordService.getDailyPassword(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java deleted file mode 100644 index 4843c2d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.FirearmRequest; -import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.FirearmService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -/** - * REST controller for firearm CRUD operations. - * - * @author zihluwang - */ -@Tag(name = "武器管理", description = "与武器有关的操作") -@RestController -@RequestMapping("/firearms") -public class FirearmController { - - private final FirearmService firearmService; - - public FirearmController(FirearmService firearmService) { - this.firearmService = firearmService; - } - - @Operation(description = "获取分页武器数据") - @Validated - @GetMapping - public PageResponse pageQuery( - @RequestParam(defaultValue = "0") @Min(0) int page, - @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, - @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "DESC") Sort.Direction direction, - @RequestParam(required = false) FirearmType type - ) { - return firearmService.pageQuery(type, PageRequest.of(page, size, Sort.by(direction, sortBy))); - } - - @Operation(description = "获取指定武器的数据") - @GetMapping("/{id}") - public FirearmResponse queryById(@PathVariable Long id) { - return firearmService.queryById(id); - } - - @RequiresAuth - @PostMapping - public FirearmResponse addFirearm(@Validated @RequestBody FirearmRequest request) { - return firearmService.addFirearm(request); - } - - @RequiresAuth - @Operation(description = "更新指定武器的数据") - @PutMapping("/{id}") - public FirearmResponse updateFirearm(@PathVariable Long id, @Validated @RequestBody FirearmRequest request) { - return firearmService.updateFirearm(id, request); - } - - @RequiresAuth - @Operation(description = "删除指定武器的数据") - @DeleteMapping("/{id}") - public void deleteFirearm(@PathVariable Long id) { - firearmService.deleteFirearm(id); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java deleted file mode 100644 index 580e1f9..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.GitHubIssueRequest; -import com.onixbyte.deltaforceguide.service.WebhookService; -import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/webhooks/github") -public class GitHubWebhookController { - - private static final Logger log = LoggerFactory.getLogger(GitHubWebhookController.class); - - private final WebhookService webhookService; - - public GitHubWebhookController(WebhookService webhookService) { - this.webhookService = webhookService; - } - - @PostMapping - public ResponseEntity handleWebhook( - @RequestHeader(GitHubWebhookHeader.EVENT) String event, - @RequestBody GitHubIssueRequest request - ) { - if (!"issues".equals(event)) { - log.debug("Ignoring non-issue event: {}", event); - return ResponseEntity.ok().build(); - } - if (!"opened".equals(request.action())) { - log.debug("Ignoring issue action: {}", request.action()); - return ResponseEntity.ok().build(); - } - webhookService.processIssueEvent(request); - return ResponseEntity.ok().build(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java deleted file mode 100644 index 4edd154..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.ErrorResponse; -import com.onixbyte.deltaforceguide.exeption.BizException; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -/** - * Global exception handler that translates exceptions into standard error responses. - * - * @author zihluwang - */ -@RestControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(BizException.class) - public ResponseEntity handleBizException(BizException exception) { - var status = exception.getStatus(); - return ResponseEntity.status(status) - .body(new ErrorResponse(exception.getMessage())); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java deleted file mode 100644 index 7eaf94c..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.ModificationBatchCreateRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.ModificationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.Positive; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * REST controller for modification CRUD operations, including batch creation and deletion. - * - * @author zihluwang - */ -@Tag(name = "改装管理", description = "对枪械改装的管理") -@RestController -@RequestMapping("/modifications") -public class ModificationController { - - private final ModificationService modificationService; - - public ModificationController(ModificationService modificationService) { - this.modificationService = modificationService; - } - - @Operation(description = "分页查询改装信息") - @Validated - @GetMapping - public PageResponse pageQuery( - @RequestParam(defaultValue = "0") @Min(0) int page, - @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, - @RequestParam(required = false) @Positive Long firearmId, - @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "DESC") Sort.Direction direction, - @RequestParam(required = false) List tags - ) { - return modificationService.pageQuery(firearmId, tags, PageRequest.of(page, size, Sort.by(direction, sortBy))); - } - - @Operation(description = "查询指定改装的信息") - @GetMapping("/{id}") - public ModificationResponse queryById(@PathVariable Long id) { - return modificationService.queryById(id); - } - - @RequiresAuth - @Operation(description = "创建改装") - @PostMapping - public ModificationResponse create(@Valid @RequestBody ModificationRequest request) { - return modificationService.create(request); - } - - @RequiresAuth - @Operation(description = "批量创建改装") - @PostMapping("/batch") - public List batchCreate(@Valid @RequestBody ModificationBatchCreateRequest request) { - return modificationService.batchCreate(request.modifications()); - } - - @RequiresAuth - @Operation(description = "修改指定改装") - @PutMapping("/{id}") - public ModificationResponse update(@PathVariable Long id, @Valid @RequestBody ModificationRequest request) { - return modificationService.update(id, request); - } - - @RequiresAuth - @Operation(description = "删除指定改装") - @DeleteMapping("/{id}") - public void delete(@PathVariable Long id) { - modificationService.delete(id); - } - - @RequiresAuth - @Operation(description = "批量删除改装") - @DeleteMapping("/batch-delete") - @Validated - public void batchDelete(@RequestParam List<@Positive Long> ids) { - modificationService.batchDelete(ids); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java deleted file mode 100644 index b9f4e59..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import com.onixbyte.deltaforceguide.service.ModificationService; - -import java.util.List; - -/** - * REST controller for retrieving available modification tags. - * - * @author zihluwang - */ -@Tag(name = "标签管理", description = "管理标签信息") -@RestController -@RequestMapping("/tags") -public class TagController { - - private final ModificationService modificationService; - - public TagController(ModificationService modificationService) { - this.modificationService = modificationService; - } - - @Operation(description = "查询指定武器或所有武器的标签") - @GetMapping - public List getTags(@RequestParam(required = false) Long firearmId) { - return modificationService.findAllTags(firearmId); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java b/server/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java deleted file mode 100644 index 67a08ef..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.service.AppService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "版本信息") -@RestController -@RequestMapping("/versions") -public class VersionController { - - private final AppService appService; - - public VersionController(AppService appService) { - this.appService = appService; - } - - @Operation(description = "获取当前应用版本号") - @GetMapping - public String getVersion() { - return appService.getVersion(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java deleted file mode 100644 index 8108db3..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.converter; - -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; - -/** - * JPA attribute converter that maps {@link FirearmType} enum to/from its integer database representation. - * - * @author zihluwang - */ -@Converter -public class FirearmTypeConverter implements AttributeConverter { - - @Override - public Integer convertToDatabaseColumn(FirearmType attribute) { - return attribute == null ? null : attribute.getCode(); - } - - @Override - public FirearmType convertToEntityAttribute(Integer dbData) { - return FirearmType.fromCode(dbData); - } -} - - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java deleted file mode 100644 index b94b61c..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; - -import java.util.ArrayList; -import java.util.List; - -/** - * Request DTO for creating or updating an accessory attached to a modification. - * - * @author zihluwang - */ -public record AccessoryRequest( - @NotBlank(message = "插槽名称不能为空") - String slotName, - @NotBlank(message = "配件名称不能为空") - String accessoryName, - List<@Valid TuningRequest> tunings -) { - public List tunings() { - return tunings == null ? new ArrayList<>() : tunings; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java deleted file mode 100644 index ade3eac..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Accessory; - -import java.util.List; - -/** - * Response DTO for an accessory attached to a modification. - * - * @author zihluwang - */ -public record AccessoryResponse( - String slotName, - String accessoryName, - List tunings -) { - public static AccessoryResponse from(Accessory accessory) { - return new AccessoryResponse( - accessory.getSlotName(), - accessory.getAccessoryName(), - accessory.getTunings() == null - ? List.of() - : accessory.getTunings().stream().map(TuningResponse::from).toList() - ); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java deleted file mode 100644 index edfa9c0..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * DTO representing a single daily-generated password for a map. - * - * @author zihluwang - */ -public record DailyPassword( - String mapName, - String password -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java deleted file mode 100644 index 5e3c12f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * DTO containing daily password data including update information and password list. - * - * @author zihluwang - */ -public record DailyPasswordData( - String updateDate, - Integer totalCount, - List passwords, - String source, - LocalDateTime lastUpdated, - Long timestamp -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java deleted file mode 100644 index 9af994f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * DTO holding metadata about the daily password source and update tracking. - * - * @author zihluwang - */ -public record DailyPasswordMetadata( - String version, - String author -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java deleted file mode 100644 index d3ce808..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * Response DTO wrapping daily password data with metadata. - * - * @author zihluwang - */ -public record DailyPasswordResponse( - String status, - String message, - DailyPasswordData data, - DailyPasswordMetadata metadata -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java deleted file mode 100644 index 11879fc..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * Standard error response body returned on API failures. - * - * @author zihluwang - */ -public record ErrorResponse( - String message -) { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java deleted file mode 100644 index 02fb855..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.enumeration.FirearmType; - -/** - * Request DTO for creating or updating a firearm. - * - * @author zihluwang - */ -public record FirearmRequest( - String name, - FirearmType type, - String level, - String calibre, - Integer fireRate, - Integer armourDamage, - Integer bodyDamage, - String review -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java deleted file mode 100644 index 0e9eaba..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; - -/** - * Response DTO for a firearm record, including associated modifications. - * - * @author zihluwang - */ -public record FirearmResponse( - Long id, - String name, - FirearmType type, - String level, - String calibre, - Integer fireRate, - Integer armourDamage, - Integer bodyDamage, - String review -) { - public static FirearmResponse from(Firearm firearm) { - return new FirearmResponse( - firearm.getId(), - firearm.getName(), - firearm.getType(), - firearm.getLevel(), - firearm.getCalibre(), - firearm.getFireRate(), - firearm.getArmourDamage(), - firearm.getBodyDamage(), - firearm.getReview() - ); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java deleted file mode 100644 index 8c4e1a6..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubIssueRequest( - String action, - GitHubWebhookIssue issue, - GitHubWebhookRepository repository, - GitHubWebhookSender sender -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java deleted file mode 100644 index 91263cc..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import java.util.List; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookIssue( - String url, - Long id, - Long number, - String title, - String body, - List labels -) { - -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java deleted file mode 100644 index 33954ab..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookLabel( - String name -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java deleted file mode 100644 index e3858d6..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookRepository( - String fullName -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java deleted file mode 100644 index 416891e..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookSender( - String login -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java deleted file mode 100644 index 106d533..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -/** - * Login request containing principle (username/email) and credential (password). - * - * @author zihluwang - */ -@Schema(description = "登录请求") -public record LoginRequest( - @NotBlank(message = "登录名称不能为空") - @Schema(description = "用户名或电子邮箱", requiredMode = Schema.RequiredMode.REQUIRED) - String principle, - @NotBlank(message = "登录口令不能为空") - @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED) - String credential -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java deleted file mode 100644 index c0aa59f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; - -import java.util.List; - -/** - * Request DTO for batch creation of modifications. - * - * @author zihluwang - */ -public record ModificationBatchCreateRequest( - @NotEmpty(message = "批量创建列表不能为空") - List<@Valid ModificationRequest> modifications -) { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java deleted file mode 100644 index 4243895..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Positive; - -import java.util.List; - -/** - * Request DTO for batch deletion of modifications by ID. - * - * @author zihluwang - */ -public record ModificationBatchDeleteRequest( - @NotEmpty(message = "批量删除ID列表不能为空") - List<@Positive(message = "ID必须为正数") Long> ids -) { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java deleted file mode 100644 index e4f6708..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - -import java.util.ArrayList; -import java.util.List; - -/** - * Request DTO for creating or updating a modification. - * - * @author zihluwang - */ -public record ModificationRequest( - @NotNull(message = "武器ID不能为空") - @Positive(message = "武器ID必须为正数") - Long firearmId, - @NotBlank(message = "改装名称不能为空") - String name, - @NotBlank(message = "改装代码不能为空") - String code, - List<@NotBlank(message = "标签不能为空") String> tags, - List<@Valid AccessoryRequest> accessories, - String note, - String author, - String videoUrl -) { - public List tags() { - return tags == null ? new ArrayList<>() : tags; - } - - public List accessories() { - return accessories == null ? new ArrayList<>() : accessories; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java deleted file mode 100644 index aca6ef5..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Modification; - -import java.util.List; - -/** - * Response DTO for a modification record including accessories and tags. - * - * @author zihluwang - */ -public record ModificationResponse( - Long id, - Long firearmId, - String name, - String code, - List tags, - List accessories, - String note, - String author, - String videoUrl -) { - public static ModificationResponse from(Modification modification) { - return new ModificationResponse( - modification.getId(), - modification.getFirearm().getId(), - modification.getName(), - modification.getCode(), - modification.getTags(), - modification.getAccessories() == null - ? List.of() - : modification.getAccessories().stream().map(AccessoryResponse::from).toList(), - modification.getNote(), - modification.getAuthor(), - modification.getVideoUrl() - ); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java deleted file mode 100644 index 20738bd..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import org.springframework.data.domain.Page; - -import java.util.List; - -/** - * Generic paginated response wrapper for list endpoints. - * - * @author zihluwang - */ -public record PageResponse( - List items, - int page, - int size, - long totalElements, - int totalPages -) { - public static PageResponse from(Page source) { - return new PageResponse<>( - source.getContent(), - source.getNumber(), - source.getSize(), - source.getTotalElements(), - source.getTotalPages() - ); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java deleted file mode 100644 index a180eb3..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * Request DTO for a tuning adjustment on an accessory. - * - * @author zihluwang - */ -public record TuningRequest( - @NotBlank(message = "调校项名称不能为空") - String tuningName, - @NotNull(message = "调校值不能为空") - Double tuningValue -) { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java deleted file mode 100644 index 405ecde..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Tuning; - -/** - * Response DTO for a tuning adjustment on an accessory. - * - * @author zihluwang - */ -public record TuningResponse( - String tuningName, - Double tuningValue -) { - public static TuningResponse from(Tuning tuning) { - return new TuningResponse( - tuning.getTuningName(), - tuning.getTuningValue() - ); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java deleted file mode 100644 index 4a2d82d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.User; - -import java.time.LocalDateTime; - -/** - * Response DTO for a user account, including associated credentials. - * - * @author zihluwang - */ -public record UserResponse( - Long id, - String username, - String email, - LocalDateTime expiration -) { - public static UserResponse from(User user, LocalDateTime expiration) { - return new UserResponse( - user.getId(), - user.getUsername(), - user.getEmail(), - expiration - ); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java deleted file mode 100644 index cde914d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Entity representing an accessory attached to a modification, stored as JSONB. - * - * @author zihluwang - */ -public class Accessory { - - private String slotName; - - private String accessoryName; - - private List tunings = new ArrayList<>(); - - public Accessory() { - } - - public String getSlotName() { - return slotName; - } - - public void setSlotName(String slotName) { - this.slotName = slotName; - } - - public String getAccessoryName() { - return accessoryName; - } - - public void setAccessoryName(String accessoryName) { - this.accessoryName = accessoryName; - } - - public List getTunings() { - return tunings; - } - - public void setTunings(List tunings) { - this.tunings = tunings; - } - - public void addTuning(Tuning tuning) { - this.tunings.add(tuning); - } - - public void removeTuning(Tuning tuning) { - this.tunings.remove(tuning); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Accessory accessory)) { - return false; - } - return Objects.equals(slotName, accessory.slotName) - && Objects.equals(accessoryName, accessory.accessoryName) - && Objects.equals(tunings, accessory.tunings); - } - - @Override - public int hashCode() { - return Objects.hash(slotName, accessoryName, tunings); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java deleted file mode 100644 index c71cee7..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import com.onixbyte.deltaforceguide.domain.converter.FirearmTypeConverter; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing a firearm in the Delta Force game. - * - * @author zihluwang - */ -@Entity -@Table(name = "firearm") -public class Firearm { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "name", nullable = false, length = 64) - private String name; - - @Column(name = "type", nullable = false) - @Convert(converter = FirearmTypeConverter.class) - private FirearmType type; - - @Column(name = "level", nullable = false) - private String level; - - @Column(name = "review", columnDefinition = "TEXT") - private String review; - - @Column(name = "calibre") - private String calibre; - - @Column(name = "fire_rate") - private Integer fireRate; - - @Column(name = "armour_damage") - private Integer armourDamage; - - @Column(name = "body_damage") - private Integer bodyDamage; - - @OneToMany(mappedBy = "firearm", cascade = CascadeType.ALL, orphanRemoval = true) - private List modifications = new ArrayList<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public FirearmType getType() { - return type; - } - - public void setType(FirearmType type) { - this.type = type; - } - - public String getLevel() { - return level; - } - - public void setLevel(String level) { - this.level = level; - } - - public String getReview() { - return review; - } - - public void setReview(String review) { - this.review = review; - } - - public String getCalibre() { - return calibre; - } - - public void setCalibre(String calibre) { - this.calibre = calibre; - } - - public Integer getFireRate() { - return fireRate; - } - - public void setFireRate(Integer fireRate) { - this.fireRate = fireRate; - } - - public Integer getArmourDamage() { - return armourDamage; - } - - public void setArmourDamage(Integer armourDamage) { - this.armourDamage = armourDamage; - } - - public Integer getBodyDamage() { - return bodyDamage; - } - - public void setBodyDamage(Integer bodyDamage) { - this.bodyDamage = bodyDamage; - } - - public List getModifications() { - return modifications; - } - - public void setModifications(List modifications) { - this.modifications = modifications; - } - - public void addModification(Modification modification) { - this.modifications.add(modification); - modification.setFirearm(this); - } - - public void removeModification(Modification modification) { - this.modifications.remove(modification); - modification.setFirearm(null); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private String name; - private FirearmType type; - private String level; - private String review; - private String calibre; - private Integer fireRate; - private Integer armourDamage; - private Integer bodyDamage; - private List modifications; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder type(FirearmType type) { - this.type = type; - return this; - } - - public Builder level(String level) { - this.level = level; - return this; - } - - public Builder review(String review) { - this.review = review; - return this; - } - - public Builder calibre(String calibre) { - this.calibre = calibre; - return this; - } - - public Builder fireRate(Integer fireRate) { - this.fireRate = fireRate; - return this; - } - - public Builder armourDamage(Integer armourDamage) { - this.armourDamage = armourDamage; - return this; - } - - public Builder bodyDamage(Integer bodyDamage) { - this.bodyDamage = bodyDamage; - return this; - } - - public Builder modifications(List modifications) { - this.modifications = modifications; - return this; - } - - public Firearm build() { - Firearm firearm = new Firearm(); - firearm.id = this.id; - firearm.name = this.name; - firearm.type = this.type; - firearm.level = this.level; - firearm.review = this.review; - firearm.calibre = this.calibre; - firearm.fireRate = this.fireRate; - firearm.armourDamage = this.armourDamage; - firearm.bodyDamage = this.bodyDamage; - firearm.modifications = this.modifications == null ? new ArrayList<>() : this.modifications; - return firearm; - } - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java deleted file mode 100644 index a1942f7..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import jakarta.persistence.*; -import org.hibernate.annotations.Type; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing a firearm modification or build configuration. - * - * @author zihluwang - */ -@Entity -@Table( - name = "modification", - indexes = { - @Index(name = "idx_modification_firearm_id", columnList = "firearm_id") - } -) -public class Modification { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "firearm_id", nullable = false, foreignKey = @ForeignKey(name = "fk_modification_firearm")) - private Firearm firearm; - - @Column(name = "name", nullable = false, length = 64) - private String name; - - @Column(name = "code", nullable = false, length = 64) - private String code; - - @Type(JsonType.class) - @Column(name = "tags", columnDefinition = "jsonb") - private List tags = new ArrayList<>(); - - @Type(JsonType.class) - @Column(name = "accessories", columnDefinition = "jsonb") - private List accessories = new ArrayList<>(); - - @Column(name = "note", columnDefinition = "TEXT") - private String note; - - @Column(name = "author", length = 64) - private String author; - - @Column(name = "video_url", length = 512) - private String videoUrl; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Firearm getFirearm() { - return firearm; - } - - public void setFirearm(Firearm firearm) { - this.firearm = firearm; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public List getAccessories() { - return accessories; - } - - public void setAccessories(List accessories) { - this.accessories = accessories; - } - - public void addAccessory(Accessory modificationAccessory) { - this.accessories.add(modificationAccessory); - } - - public void removeAccessory(Accessory modificationAccessory) { - this.accessories.remove(modificationAccessory); - } - - public String getNote() { - return note; - } - - public void setNote(String note) { - this.note = note; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getVideoUrl() { - return videoUrl; - } - - public void setVideoUrl(String videoUrl) { - this.videoUrl = videoUrl; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private Firearm firearm; - private String name; - private String code; - private List tags; - private List accessories; - private String note; - private String author; - private String videoUrl; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder firearm(Firearm firearm) { - this.firearm = firearm; - return this; - } - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder code(String code) { - this.code = code; - return this; - } - - public Builder tags(List tags) { - this.tags = tags; - return this; - } - - public Builder accessories(List accessories) { - this.accessories = accessories; - return this; - } - - public Builder note(String note) { - this.note = note; - return this; - } - - public Builder author(String author) { - this.author = author; - return this; - } - - public Builder videoUrl(String videoUrl) { - this.videoUrl = videoUrl; - return this; - } - - public Modification build() { - Modification modification = new Modification(); - modification.id = this.id; - modification.firearm = this.firearm; - modification.name = this.name; - modification.code = this.code; - modification.tags = this.tags == null ? new ArrayList<>() : this.tags; - modification.accessories = this.accessories == null ? new ArrayList<>() : this.accessories; - modification.note = this.note; - modification.author = this.author; - modification.videoUrl = this.videoUrl; - return modification; - } - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java deleted file mode 100644 index a4eb1f1..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import java.util.Objects; - -/** - * Entity representing a tuning adjustment for an accessory, stored as JSONB within Accessory. - * - * @author zihluwang - */ -public class Tuning { - - private String tuningName; - private Double tuningValue; - - public Tuning() { - } - - public String getTuningName() { - return tuningName; - } - - public void setTuningName(String tuningName) { - this.tuningName = tuningName; - } - - public Double getTuningValue() { - return tuningValue; - } - - public void setTuningValue(Double tuningValue) { - this.tuningValue = tuningValue; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Tuning tuning)) { - return false; - } - return Objects.equals(tuningName, tuning.tuningName) - && Objects.equals(tuningValue, tuning.tuningValue); - } - - @Override - public int hashCode() { - return Objects.hash(tuningName, tuningValue); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java deleted file mode 100644 index d696b2d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing an application user with authentication credentials. - * - * @author zihluwang - */ -@Entity -@Table(name = "app_user") -public class User { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "username", nullable = false) - private String username; - - @Column(name = "email", nullable = false) - private String email; - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List credentials = new ArrayList<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public List getCredentials() { - return credentials; - } - - public void setCredentials(List credentials) { - this.credentials = credentials; - } - - public void addCredential(UserCredential credential) { - this.credentials.add(credential); - credential.setUser(this); - } - - public void removeCredential(UserCredential credential) { - this.credentials.remove(credential); - credential.setUser(null); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private String username; - private String email; - private List credentials; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder username(String username) { - this.username = username; - return this; - } - - public Builder email(String email) { - this.email = email; - return this; - } - - public Builder credentials(List credentials) { - this.credentials = credentials; - return this; - } - - public User build() { - User user = new User(); - user.id = this.id; - user.username = this.username; - user.email = this.email; - user.credentials = this.credentials == null ? new ArrayList<>() : this.credentials; - return user; - } - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java deleted file mode 100644 index 2ecdd37..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.AttributeOverride; -import jakarta.persistence.AttributeOverrides; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.MapsId; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -/** - * Entity representing a user credential linked to an authentication provider. - * - * @author zihluwang - */ -@Entity -@Table(name = "app_user_credential") -public class UserCredential { - - @EmbeddedId - @AttributeOverrides({ - @AttributeOverride(name = "userId", column = @Column(name = "user_id")), - @AttributeOverride(name = "provider", column = @Column(name = "provider")) - }) - private UserCredentialId id = new UserCredentialId(); - - @MapsId("userId") - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_credential_user")) - private User user; - - @Column(name = "credential", nullable = false) - private String credential; - - public UserCredentialId getId() { - return id; - } - - public void setId(UserCredentialId id) { - this.id = id; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setUserId(user == null ? null : user.getId()); - } - - public Long getUserId() { - return id == null ? null : id.getUserId(); - } - - public void setUserId(Long userId) { - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setUserId(userId); - } - - public String getProvider() { - return id == null ? null : id.getProvider(); - } - - public void setProvider(String provider) { - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setProvider(provider); - } - - public String getCredential() { - return credential; - } - - public void setCredential(String credential) { - this.credential = credential; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private UserCredentialId id; - private User user; - private Long userId; - private String provider; - private String credential; - - public Builder id(UserCredentialId id) { - this.id = id; - return this; - } - - public Builder user(User user) { - this.user = user; - return this; - } - - public Builder userId(Long userId) { - this.userId = userId; - return this; - } - - public Builder provider(String provider) { - this.provider = provider; - return this; - } - - public Builder credential(String credential) { - this.credential = credential; - return this; - } - - public UserCredential build() { - UserCredential userCredential = new UserCredential(); - userCredential.id = this.id == null ? new UserCredentialId() : this.id; - userCredential.user = this.user; - if (this.user != null) { - userCredential.id.setUserId(this.user.getId()); - } - if (this.userId != null) { - userCredential.id.setUserId(this.userId); - } - if (this.provider != null) { - userCredential.id.setProvider(this.provider); - } - userCredential.credential = this.credential; - return userCredential; - } - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java b/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java deleted file mode 100644 index 1e25f45..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.Embeddable; - -import java.io.Serializable; -import java.util.Objects; - -/** - * Composite key for the UserCredential entity, combining user ID and provider. - * - * @author zihluwang - */ -@Embeddable -public class UserCredentialId implements Serializable { - - private Long userId; - - private String provider; - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UserCredentialId that = (UserCredentialId) o; - return Objects.equals(userId, that.userId) && Objects.equals(provider, that.provider); - } - - @Override - public int hashCode() { - return Objects.hash(userId, provider); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long userId; - private String provider; - - public Builder userId(Long userId) { - this.userId = userId; - return this; - } - - public Builder provider(String provider) { - this.provider = provider; - return this; - } - - public UserCredentialId build() { - UserCredentialId id = new UserCredentialId(); - id.userId = this.userId; - id.provider = this.provider; - return id; - } - } -} - - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java b/server/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java deleted file mode 100644 index 77bdbdb..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.onixbyte.deltaforceguide.enumeration; - -/** - * Enumeration of firearm types in the Delta Force game. - * Each type is associated with an integer code used for database persistence. - * - * @author zihluwang - */ -public enum FirearmType { - - RIFLE(0), - SUB_MACHINE_GUN(1), - SHOTGUN(2), - LIGHT_MACHINE_GUN(3), - DESIGNATED_MARKSMAN_RIFLE(4), - SNIPER_RIFLE(5), - PISTOL(6), - SPECIAL(7); - - private final int code; - - FirearmType(int code) { - this.code = code; - } - - public int getCode() { - return code; - } - - /** - * Resolve a FirearmType from its integer code. - * - * @param code the integer code, may be null - * @return the corresponding FirearmType, or null if the code is null - * @throws IllegalArgumentException if the code does not match any known type - */ - public static FirearmType fromCode(Integer code) { - if (code == null) { - return null; - } - - for (FirearmType type : values()) { - if (type.code == code) { - return type; - } - } - - throw new IllegalArgumentException("Unknown FirearmType code: " + code); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java b/server/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java deleted file mode 100644 index d1b7ff2..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.onixbyte.deltaforceguide.exeption; - -import org.springframework.http.HttpStatus; - -/** - * Custom runtime exception that carries an HTTP status code for API error responses. - * - * @author zihluwang - */ -public class BizException extends RuntimeException { - - /** - * The HTTP status code associated with this business exception. - *

- * This status code indicates the appropriate HTTP response status that should be returned to - * clients when this exception occurs. It enables consistent error handling across - * REST API endpoints. - */ - private final HttpStatus status; - - public BizException(String message) { - super(message); - this.status = HttpStatus.INTERNAL_SERVER_ERROR; - } - - public BizException(HttpStatus status, String message) { - super(message); - this.status = status; - } - - /** - * Returns the HTTP status code associated with this business exception. - * - * @return the HTTP status code that should be used in the error response - */ - public HttpStatus getStatus() { - return status; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java b/server/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java deleted file mode 100644 index e2068a6..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.onixbyte.deltaforceguide.filter; - -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.onixbyte.deltaforceguide.client.TokenClient; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.UserManager; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import com.onixbyte.deltaforceguide.service.CookieService; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.util.WebUtils; - -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.Objects; -import java.util.Optional; - -/** - * Servlet filter that extracts and validates JWT tokens from httpOnly cookies for each request. - * - * @author zihluwang - */ -@Component -public class TokenAuthenticationFilter extends OncePerRequestFilter { - - private final static Logger log = LoggerFactory.getLogger(TokenAuthenticationFilter.class); - private static final Duration ACCESS_TOKEN_RENEW_THRESHOLD = Duration.ofMinutes(5); - - private final UserManager userManager; - private final TokenClient tokenClient; - private final CookieService cookieService; - private final HandlerExceptionResolver handlerExceptionResolver; - - public TokenAuthenticationFilter( - UserManager userManager, - TokenClient tokenClient, - CookieService cookieService, - HandlerExceptionResolver handlerExceptionResolver - ) { - this.userManager = userManager; - this.tokenClient = tokenClient; - this.cookieService = cookieService; - this.handlerExceptionResolver = handlerExceptionResolver; - } - - /** - * Extracts JWT from httpOnly cookie, validates it, and sets the security context. - * - * @param request the HTTP request - * @param response the HTTP response - * @param filterChain the filter chain - */ - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain - ) throws ServletException, IOException { - var token = Optional.ofNullable(WebUtils.getCookie(request, "AccessToken")) - .map(Cookie::getValue) - .orElse(null); - if (Objects.isNull(token) || token.isBlank()) { - filterChain.doFilter(request, response); - return; - } - - try { - var decodedToken = tokenClient.verifyToken(token); - var username = decodedToken.getSubject(); - - var userWrapper = userManager.findByUsername(username); - if (userWrapper.isEmpty()) { - throw new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录"); - } - - var user = userWrapper.get(); - var authentication = UsernamePasswordAuthentication.authenticated(user); - SecurityContextHolder.getContext().setAuthentication(authentication); - - if (shouldRenew(decodedToken.getExpiresAt().toInstant())) { - var renewedToken = tokenClient.generateToken(user); - var renewedTokenCookie = cookieService.buildCookie("AccessToken", renewedToken); - response.addHeader(HttpHeaders.SET_COOKIE, renewedTokenCookie.toString()); - } - - filterChain.doFilter(request, response); - } catch (JWTVerificationException e) { - log.error("JWT verification failed.", e); - handlerExceptionResolver.resolveException(request, response, null, - new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录")); - } catch (BizException e) { - handlerExceptionResolver.resolveException(request, response, null, e); - } - } - - private boolean shouldRenew(Instant expiresAt) { - return Duration.between(Instant.now(), expiresAt).compareTo(ACCESS_TOKEN_RENEW_THRESHOLD) < 0; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java b/server/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java deleted file mode 100644 index ddfd071..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.onixbyte.deltaforceguide.filter; - -import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper; -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Component -public class WebhookFilter implements Filter { - @Override - public void doFilter( - ServletRequest request, - ServletResponse response, - FilterChain chain - ) throws IOException, ServletException { - var httpRequest = (HttpServletRequest) request; - var wrappedRequest = new RepeatedlyReadRequestWrapper(httpRequest); - chain.doFilter(wrappedRequest, response); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java b/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java deleted file mode 100644 index c1b552a..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.onixbyte.deltaforceguide.interceptor; - -import com.onixbyte.crypto.util.CryptoUtil; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.WebhookManager; -import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader; -import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Verifies GitHub webhook requests by validating the {@code X-Hub-Signature-256} - * header against the configured secret using HMAC-SHA256. - * - *

Verification is skipped when no secret is configured. The signature format is - * {@code sha256=} as documented by GitHub. - */ -@Component -public class GitHubWebhookInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory.getLogger(GitHubWebhookInterceptor.class); - - private final WebhookManager webhookManager; - - public GitHubWebhookInterceptor(WebhookManager webhookManager) { - this.webhookManager = webhookManager; - } - - @Override - public boolean preHandle( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull Object handler - ) { - if (!(request instanceof RepeatedlyReadRequestWrapper req)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, - "Request body is not readable"); - } - - var secret = webhookManager.secret(); - if (secret == null || secret.isBlank()) { - log.debug("No GitHub webhook secret configured, skipping signature verification"); - return true; - } - - var signatureHeader = req.getHeader(GitHubWebhookHeader.SIGNATURE_256); - if (signatureHeader == null || signatureHeader.isBlank()) { - log.warn("Missing {} header from ip={}", - GitHubWebhookHeader.SIGNATURE_256, request.getRemoteAddr()); - throw new BizException(HttpStatus.UNAUTHORIZED, - "Missing webhook signature header"); - } - - var body = req.getBodyString(); - try { - var computed = "sha256=" + CryptoUtil.hmacSha256(body, secret); - - if (!MessageDigest.isEqual( - computed.getBytes(StandardCharsets.UTF_8), - signatureHeader.getBytes(StandardCharsets.UTF_8))) { - log.warn("Invalid webhook signature from ip={}", request.getRemoteAddr()); - throw new BizException(HttpStatus.UNAUTHORIZED, "Invalid webhook signature"); - } - - return true; - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - log.error("Failed to compute HMAC-SHA256", e); - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, - "Failed to verify webhook signature"); - } - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java b/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java deleted file mode 100644 index c7aac50..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onixbyte.deltaforceguide.interceptor; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -@Component -public class TrafficInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory.getLogger(TrafficInterceptor.class); - - @Override - public boolean preHandle( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull Object handler - ) { - var ip = resolveClientIp(request); - var method = request.getMethod(); - var uri = request.getRequestURI(); - var query = request.getQueryString(); - var contentType = request.getContentType(); - var contentLength = request.getContentLength(); - var userAgent = request.getHeader("User-Agent"); - - log.debug("Request method={}, uri={}, query={}, ip={}, content-type={}, content-length={}, user-agent={}", - method, uri, query, ip, contentType, contentLength, userAgent); - return true; - } - - private String resolveClientIp(HttpServletRequest request) { - var xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isBlank()) { - return xForwardedFor.split(",")[0].trim(); - } - var xRealIp = request.getHeader("X-Real-IP"); - if (xRealIp != null && !xRealIp.isBlank()) { - return xRealIp.trim(); - } - return request.getRemoteAddr(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java deleted file mode 100644 index 378fe9b..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.AppProperties; -import org.springframework.stereotype.Component; - -@Component -public class AppManager { - - private final AppProperties appProperties; - - public AppManager(AppProperties appProperties) { - this.appProperties = appProperties; - } - - /** - * Retrieves the application version. - * - * @return the version string of this application - */ - public String getVersion() { - return "v%s-%s by @%s".formatted( - appProperties.version(), - appProperties.channel(), - appProperties.vendor() - ); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java deleted file mode 100644 index f6ef16f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.CookieProperties; -import org.springframework.boot.web.server.Cookie; -import org.springframework.stereotype.Component; - -import java.time.Duration; - -/** - * Manager providing cookie construction operations with configurable properties. - * - * @author zihluwang - */ -@Component -public class CookieManager { - - private final CookieProperties cookieProperties; - - public CookieManager(CookieProperties cookieProperties) { - this.cookieProperties = cookieProperties; - } - - public Boolean getHttpOnly() { - return cookieProperties.httpOnly(); - } - - public Boolean getSecure() { - return cookieProperties.secure(); - } - - public Cookie.SameSite getSameSite() { - return cookieProperties.sameSite(); - } - - public String getPath() { - return cookieProperties.path(); - } - - public Duration getMaxAge() { - return cookieProperties.maxAge(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java deleted file mode 100644 index 6b6ac59..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.shared.JacksonModules; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; - -import java.time.Duration; -import java.time.LocalDate; -import java.util.Objects; - -/** - * Manager for daily password data access and caching coordination. - * - * @author zihluwang - */ -@Component -public class DailyPasswordManager { - - private static final String CACHE_KEY_PREFIX = "daily-password:"; - - private final RestClient restClient; - private final RedisTemplate redisTemplate; - - @Autowired - public DailyPasswordManager( - RestClient.Builder restClientBuilder, - RedisTemplate redisTemplate - ) { - var snakeCaseMapper = new ObjectMapper(); - snakeCaseMapper.setPropertyNamingStrategy( - PropertyNamingStrategies.SnakeCaseStrategy.INSTANCE); - snakeCaseMapper.configure( - DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - snakeCaseMapper.registerModule(JacksonModules.DATE_TIME_MODULE); - - this.restClient = restClientBuilder - .baseUrl("https://tmini.net/api") - .messageConverters(converters -> { - converters.removeIf( - MappingJackson2HttpMessageConverter.class::isInstance); - converters.add( - new MappingJackson2HttpMessageConverter(snakeCaseMapper)); - }) - .build(); - this.redisTemplate = redisTemplate; - } - - /** - * Retrieves the daily password from cache or generates a new one. - * @return the daily password response - */ - public DailyPasswordResponse getDailyPassword() { - var key = CACHE_KEY_PREFIX + LocalDate.now(); - - var cached = redisTemplate.opsForValue().get(key); - if (cached != null) { - return (DailyPasswordResponse) cached; - } - - var response = restClient.get() - .uri((uriBuilder) -> uriBuilder - .path("/sjzmm") - .queryParam("ckey", "") - .queryParam("type", "json") - .build()) - .retrieve() - .body(DailyPasswordResponse.class); - - if (Objects.isNull(response)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "暂无每日密码数据。"); - } - - redisTemplate.opsForValue().set(key, response, Duration.ofDays(1L)); - return response; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java deleted file mode 100644 index f7defab..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.Accessory; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import com.onixbyte.deltaforceguide.domain.entity.Tuning; -import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.TuningRequest; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import com.onixbyte.deltaforceguide.repository.ModificationRepository; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -@Component -public class ModificationManager { - - private final ModificationRepository modificationRepository; - private final FirearmRepository firearmRepository; - - public ModificationManager( - ModificationRepository modificationRepository, - FirearmRepository firearmRepository - ) { - this.modificationRepository = modificationRepository; - this.firearmRepository = firearmRepository; - } - - @Transactional - public ModificationResponse create(ModificationRequest request) { - var firearm = firearmRepository.findById(request.firearmId()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found: " + request.firearmId())); - var modification = toEntity(request, firearm); - return ModificationResponse.from(modificationRepository.save(modification)); - } - - @Transactional - public List batchCreate(List requests) { - var firearmIds = requests.stream() - .map(ModificationRequest::firearmId) - .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)); - - Map firearmMap = new HashMap<>(); - firearmRepository.findAllById(firearmIds) - .forEach(firearm -> firearmMap.put(firearm.getId(), firearm)); - - if (firearmMap.size() != firearmIds.size()) { - var missing = firearmIds.stream() - .filter((id) -> !firearmMap.containsKey(id)) - .toList(); - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found: " + missing); - } - - var modifications = requests.stream() - .map(req -> toEntity(req, firearmMap.get(req.firearmId()))) - .toList(); - return modificationRepository.saveAll(modifications) - .stream() - .map(ModificationResponse::from) - .toList(); - } - - public Long resolveFirearmId(Long firearmId, String firearmName) { - if (firearmId != null) { - return firearmId; - } - if (firearmName == null || firearmName.isBlank()) { - return null; - } - var matches = firearmRepository.findByName(firearmName); - if (matches.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found by name: " + firearmName); - } - return matches.getFirst().getId(); - } - - private Modification toEntity(ModificationRequest request, Firearm firearm) { - return Modification.builder() - .firearm(firearm) - .name(request.name()) - .code(request.code()) - .tags(request.tags()) - .accessories(toAccessories(request.accessories())) - .note(request.note()) - .author(request.author()) - .videoUrl(request.videoUrl()) - .build(); - } - - private List toAccessories(List requests) { - if (requests == null) { - return new ArrayList<>(); - } - return requests.stream().map(this::toAccessory).toList(); - } - - private Accessory toAccessory(AccessoryRequest request) { - var accessory = new Accessory(); - accessory.setSlotName(request.slotName()); - accessory.setAccessoryName(request.accessoryName()); - accessory.setTunings(toTunings(request.tunings())); - return accessory; - } - - private List toTunings(List requests) { - if (requests == null) { - return new ArrayList<>(); - } - return requests.stream().map(this::toTuning).toList(); - } - - private Tuning toTuning(TuningRequest request) { - var tuning = new Tuning(); - tuning.setTuningName(request.tuningName()); - tuning.setTuningValue(request.tuningValue()); - return tuning; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java deleted file mode 100644 index d77c687..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.repository.UserCredentialRepository; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Optional; - -/** - * Manager for user credential persistence and authentication data access. - * - * @author zihluwang - */ -@Component -public class UserCredentialManager { - - private final UserCredentialRepository userCredentialRepository; - - public UserCredentialManager(UserCredentialRepository userCredentialRepository) { - this.userCredentialRepository = userCredentialRepository; - } - - /** - * Finds all credentials belonging to a specific user. - * - * @param userId the user ID - * @return list of matching credentials - */ - public List findAllByUserId(Long userId) { - return userCredentialRepository.findAllByUserId(userId); - } - - /** - * Finds a credential for a specific user and provider combination. - * - * @param userId the user ID - * @param provider the authentication provider - * @return the matching credential, if found - */ - public Optional findByUserIdAndProvider(Long userId, String provider) { - return userCredentialRepository.findByUserIdAndProvider(userId, provider); - } - - /** - * Persists a new or updated credential. - * - * @param userCredential the credential to save - * @return the saved credential - */ - public UserCredential save(UserCredential userCredential) { - return userCredentialRepository.save(userCredential); - } - - /** - * Deletes a credential for a specific user and provider. - * - * @param userId the user ID - * @param provider the authentication provider - */ - public void deleteByUserIdAndProvider(Long userId, String provider) { - userCredentialRepository.deleteByUserIdAndProvider(userId, provider); - } - - /** - * Deletes all credentials belonging to a user. - * - * @param userId the user ID - */ - public void deleteAllByUserId(Long userId) { - userCredentialRepository.deleteAllByUserId(userId); - } -} - - - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java deleted file mode 100644 index d63c0f7..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.repository.UserRepository; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; - -/** - * Manager for user entity persistence and query operations. - * - * @author zihluwang - */ -@Component -public class UserManager { - - private final UserRepository userRepository; - - public UserManager(UserRepository userRepository) { - this.userRepository = userRepository; - } - - /** - * Finds a user by their ID. - * - * @param id the user ID - * @return the matching user, if found - */ - public Optional findById(Long id) { - return userRepository.findById(id); - } - - /** - * Retrieves all registered users. - * @return list of all users - */ - public List findAll() { - return userRepository.findAll(); - } - - /** - * Finds a user by their username. - * - * @param username the username to search for - * @return the matching user, if found - */ - public Optional findByUsername(String username) { - return userRepository.findByUsername(username); - } - - @Transactional(readOnly = true) - public Optional findByEmail(String email) { - return userRepository.findByEmail(email); - } - - /** - * Persists a new or updated user. - * - * @param user the user to save - * @return the saved user - */ - public User save(User user) { - return userRepository.save(user); - } - - /** - * Deletes a user by their ID. - * - * @param id the user ID to delete - */ - public void deleteById(Long id) { - userRepository.deleteById(id); - } - - /** - * Finds a user by their username or email address. - * - * @param principal the username or email to search for - * @return the matching user, if found - */ - public Optional findByUsernameOrEmail(String principal) { - return userRepository.findByUsernameOrEmail(principal); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java b/server/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java deleted file mode 100644 index 21c5b52..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.GitHubWebhookProperties; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class WebhookManager { - - private final GitHubWebhookProperties gitHubWebhookProperties; - - public WebhookManager(GitHubWebhookProperties gitHubWebhookProperties) { - this.gitHubWebhookProperties = gitHubWebhookProperties; - } - - public String secret() { - return gitHubWebhookProperties.secret(); - } - - public List allowedUsers() { - return gitHubWebhookProperties.allowedUsers(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java b/server/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java deleted file mode 100644 index b13bca2..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "app.common") -public record AppProperties( - String version, - String channel, - String vendor -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java b/server/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java deleted file mode 100644 index 0cfde08..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.DefaultValue; -import org.springframework.boot.web.server.Cookie; - -import java.time.Duration; - -/** - * Configuration properties for HTTP cookies used in authentication, prefixed with "app.cookie". - * - * @param httpOnly whether the cookie is httpOnly - * @param secure whether the cookie is secure - * @param path the cookie path - * @param maxAge the maximum age of the cookie - * @param sameSite the SameSite policy for the cookie - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.cookie") -public record CookieProperties( - @DefaultValue("true") Boolean httpOnly, - @DefaultValue("true") Boolean secure, - @DefaultValue("/") String path, - @DefaultValue("PT2H") Duration maxAge, - @DefaultValue("LAX") Cookie.SameSite sameSite -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java b/server/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java deleted file mode 100644 index 193640f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.DefaultValue; -import org.springframework.http.HttpMethod; - -import java.time.Duration; - -/** - * Configuration properties for CORS settings, prefixed with "app.cors". - * - * @param allowedHeaders headers allowed in CORS requests - * @param allowedMethods HTTP methods allowed in CORS requests - * @param allowedOrigins origins permitted to make cross-origin requests - * @param allowCredentials whether credentials are allowed in CORS requests - * @param allowPrivateNetwork whether private network access is permitted - * @param maxAge how long the CORS preflight response may be cached - * @param exposedHeaders headers exposed to the client in CORS responses - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.cors") -public record CorsProperties( - @DefaultValue({"Content-Type", "Authorization"}) - String[] allowedHeaders, - @DefaultValue({"GET", "POST", "PUT", "PATCH", "DELETE"}) - HttpMethod[] allowedMethods, - String[] allowedOrigins, - boolean allowCredentials, - boolean allowPrivateNetwork, - @DefaultValue("PT2H") - Duration maxAge, - @DefaultValue({"Content-Type", "Authorization"}) - String[] exposedHeaders -) { -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java b/server/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java deleted file mode 100644 index 673051a..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.List; - -@ConfigurationProperties(prefix = "app.webhook.github") -public record GitHubWebhookProperties( - String secret, - List allowedUsers -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java b/server/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java deleted file mode 100644 index 74c693c..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.time.Duration; - -/** - * Configuration properties for JWT token generation and validation, prefixed with "app.jwt". - * - * @param issuer the JWT issuer claim - * @param secret the signing secret for JWT tokens - * @param validTime the duration for which a token remains valid - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.jwt") -public record TokenProperties( - String issuer, - String secret, - Duration validTime -) { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java b/server/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java deleted file mode 100644 index 46c84b9..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * Spring Data JPA repository for {@link Firearm} entity operations. - * - * @author zihluwang - */ -@Repository -public interface FirearmRepository extends JpaRepository { - - Page findAllByType(FirearmType type, Pageable pageable); - - List findByName(String name); -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java b/server/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java deleted file mode 100644 index 3d9fcc0..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import org.jspecify.annotations.NonNull; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link Modification} entity operations, - * including native JSONB tag filtering for Postgres. - * - * @author zihluwang - */ -@Repository -public interface ModificationRepository extends JpaRepository { - - @EntityGraph(attributePaths = {"firearm"}) - Page findAllBy(Pageable pageable); - - @EntityGraph(attributePaths = {"firearm"}) - Page findAllByFirearm_Id(Long firearmId, Pageable pageable); - - @Override - @EntityGraph(attributePaths = {"firearm"}) - @NonNull - Optional findById(@NonNull Long id); - - /** - * Page query modifications with optional firearm and JSONB tag filtering. - * - * @param firearmId optional firearm ID filter (nullable) - * @param tagsJson optional JSON array of tags to match via Postgres {@code @>} operator (nullable) - * @param pageable pagination information - * @return a page of matching modifications - */ - @Query(value = """ - SELECT * FROM modification m - WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) - AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) - """, - countQuery = """ - SELECT count(*) FROM modification m - WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) - AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) - """, - nativeQuery = true) - Page pageQueryByFirearmAndTags(@Param("firearmId") Long firearmId, @Param("tagsJson") String tagsJson, Pageable pageable); - - /** - * Retrieve all distinct tag values from modifications, optionally filtered by firearm. - * - * @param firearmId optional firearm ID filter (nullable) - * @return list of distinct tag strings - */ - @Query(value = "SELECT DISTINCT jsonb_array_elements_text(cast(tags as jsonb)) FROM modification WHERE (:firearmId IS NULL OR firearm_id = :firearmId)", nativeQuery = true) - List findAllTags(@Param("firearmId") Long firearmId); -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java b/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java deleted file mode 100644 index 53776e3..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link UserCredential} entity operations. - * - * @author zihluwang - */ -@Repository -public interface UserCredentialRepository extends JpaRepository { - - /** - * Find all credentials belonging to a given user. - * - * @param userId the user ID - * @return list of matching credentials - */ - @EntityGraph(attributePaths = {"user"}) - @Query(""" - select uc - from UserCredential uc - where uc.user.id = :userId - """) - List findAllByUserId(@Param("userId") Long userId); - - /** - * Find a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider identifier - * @return an optional containing the matching credential, or empty if not found - */ - @EntityGraph(attributePaths = {"user"}) - @Query(""" - select uc - from UserCredential uc - where uc.user.id = :userId - and uc.id.provider = :provider - """) - Optional findByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider); - - /** - * Delete a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider identifier - */ - @Modifying - @Query(""" - delete from UserCredential uc - where uc.user.id = :userId - and uc.id.provider = :provider - """) - void deleteByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider); - - /** - * Delete all credentials for a given user. - * - * @param userId the user ID - */ - @Modifying - @Query(""" - delete from UserCredential uc - where uc.user.id = :userId - """) - void deleteAllByUserId(@Param("userId") Long userId); -} - - - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java b/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java deleted file mode 100644 index 7a0a363..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import org.jspecify.annotations.NonNull; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link User} entity operations. - * - * @author zihluwang - */ -@Repository -public interface UserRepository extends JpaRepository { - - @Override - @EntityGraph(attributePaths = {"credentials"}) - @NonNull - Optional findById(@NonNull Long id); - - @EntityGraph(attributePaths = {"credentials"}) - Optional findByUsername(String username); - - @EntityGraph(attributePaths = {"credentials"}) - Optional findByEmail(String email); - - boolean existsByUsername(String username); - - boolean existsByEmail(String email); - - /** - * Find a user by either username or email. - * - * @param principal the username or email to search for - * @return an optional containing the matching user, or empty if not found - */ - @EntityGraph(attributePaths = {"credentials"}) - @Query(""" - select u - from User u - where u.username = :principal - or u.email = :principal - """) - Optional findByUsernameOrEmail(@Param("principal") String principal); -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java b/server/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java deleted file mode 100644 index 9b6b014..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.onixbyte.deltaforceguide.security.annotation; - -import org.springframework.security.access.prepost.PreAuthorize; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark controller endpoints that require authentication. - * - * @author zihluwang - */ -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("isAuthenticated()") -public @interface RequiresAuth { -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java b/server/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java deleted file mode 100644 index 9bd2ae3..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.security.authentication; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.CredentialsContainer; -import org.springframework.security.core.GrantedAuthority; - -import java.util.Collection; -import java.util.List; - -/** - * Custom authentication token for username/password-based login flows. - * - * @author zihluwang - */ -public class UsernamePasswordAuthentication implements Authentication, CredentialsContainer { - private final String username; - private String password; - private boolean authenticated; - private User user; - - private UsernamePasswordAuthentication(String username, String password, boolean authenticated, User user) { - this.username = username; - this.password = password; - this.authenticated = authenticated; - this.user = user; - } - - public static UsernamePasswordAuthentication unauthenticated(String username, String password) { - return new UsernamePasswordAuthentication(username, password, false, null); - } - - public static UsernamePasswordAuthentication authenticated(User user) { - return new UsernamePasswordAuthentication(user.getUsername(), null, true, user); - } - - @Override - public Collection getAuthorities() { - return List.of(); - } - - @Override - public String getCredentials() { - return password; - } - - @Override - public User getDetails() { - return user; - } - - @Override - public String getPrincipal() { - return username; - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void setAuthenticated(boolean authenticated) throws IllegalArgumentException { - this.authenticated = authenticated; - } - - @Override - public String getName() { - return username; - } - - @Override - public void eraseCredentials() { - this.password = null; - } - - public void setDetails(User user) { - this.user = user; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java b/server/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java deleted file mode 100644 index 01e6baa..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.onixbyte.deltaforceguide.security.provider; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.UserManager; -import com.onixbyte.deltaforceguide.repository.UserCredentialRepository; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import com.onixbyte.deltaforceguide.shared.CredentialProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Example; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -/** - * Authentication provider that validates username/password credentials against stored BCrypt hashes. - * - * @author zihluwang - */ -@Component -public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider { - - private static final Logger log = LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class); - private final UserManager userManager; - private final PasswordEncoder passwordEncoder; - private final UserCredentialRepository userCredentialRepository; - - @Autowired - public UsernamePasswordAuthenticationProvider( - UserManager userManager, - PasswordEncoder passwordEncoder, - UserCredentialRepository userCredentialRepository - ) { - this.userManager = userManager; - this.passwordEncoder = passwordEncoder; - this.userCredentialRepository = userCredentialRepository; - } - - /** - * Validates the username/password credentials against stored BCrypt hashes. - * - * @param authentication the authentication request object - * @return a fully authenticated object including user details - */ - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "用户认证失败,请稍后再试。"); - } - - // get userContainer from database - var userContainer = userManager.findByUsernameOrEmail(usernamePasswordAuthentication.getPrincipal()); - if (userContainer.isEmpty()) { - log.error("User {} is trying to authenticate but no userContainer found.", usernamePasswordAuthentication.getPrincipal()); - throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); - } - - var user = userContainer.get(); - - var userCredentialExample = new UserCredential(); - userCredentialExample.setUserId(user.getId()); - userCredentialExample.setProvider(CredentialProvider.LOCAL); - - // get userContainer credentials from database - var userCredentials = userCredentialRepository.findOne(Example.of(userCredentialExample)) - .orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请联系管理员处理。")); - - // validate password - if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) { - log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal()); - throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); - } - - // erase credentials - usernamePasswordAuthentication.eraseCredentials(); - - // set values - usernamePasswordAuthentication.setAuthenticated(true); - usernamePasswordAuthentication.setDetails(user); - - return usernamePasswordAuthentication; - } - - /** - * Checks if this provider supports the given authentication type. - * - * @param authentication the authentication class to check - * @return true if this provider supports the given type - */ - @Override - public boolean supports(Class authentication) { - return UsernamePasswordAuthentication.class.isAssignableFrom(authentication); - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java deleted file mode 100644 index 68f50bd..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.manager.AppManager; -import org.springframework.stereotype.Service; - -@Service -public class AppService { - - private final AppManager appManager; - - public AppService(AppManager appManager) { - this.appManager = appManager; - } - - public String getVersion() { - return appManager.getVersion(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java deleted file mode 100644 index da41902..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.LoginRequest; -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.stereotype.Service; - -/** - * Service handling user authentication, login, and session management. - * - * @author zihluwang - */ -@Service -public class AuthService { - - private static final Logger log = LoggerFactory.getLogger(AuthService.class); - private final AuthenticationManager authenticationManager; - - public AuthService(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - /** - * Authenticates a user with the given login credentials. - *

- * Delegates authentication to Spring Security's {@link AuthenticationManager} and verifies - * that the result is of the expected {@link UsernamePasswordAuthentication} type. - * - * @param request the login credentials containing principle and password - * @return the authenticated {@link User} - * @throws BizException if authentication fails or the result type is unexpected - */ - public User login(LoginRequest request) { - var _authentication = authenticationManager.authenticate(UsernamePasswordAuthentication - .unauthenticated(request.principle(), request.credential())); - if (!(_authentication instanceof UsernamePasswordAuthentication authentication)) { - log.error( - "Type mismatched, required type is UsernamePasswordAuthentication but got {}.", - _authentication.getClass() - ); - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "登录服务异常,请稍后再试。"); - } - - return authentication.getDetails(); - } - -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java deleted file mode 100644 index eb93086..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.manager.CookieManager; -import org.springframework.http.ResponseCookie; -import org.springframework.stereotype.Service; - -import java.time.Duration; - -/** - * Service for building HTTP cookies with configurable properties. - * - * @author zihluwang - */ -@Service -public class CookieService { - - private final CookieManager cookieManager; - - public CookieService(CookieManager cookieManager) { - this.cookieManager = cookieManager; - } - - /** - * Builds a response cookie with the default max age from configuration. - * - * @param cookieName the cookie name - * @param value the cookie value - * @return a configured ResponseCookie - */ - public ResponseCookie buildCookie(String cookieName, String value) { - return buildCookieInternal(cookieName, value, cookieManager.getMaxAge()); - } - - /** - * Builds a response cookie with a custom valid duration. - * - * @param cookieName the cookie name - * @param value the cookie value - * @param validDuration the cookie's max age - * @return a configured ResponseCookie - */ - public ResponseCookie buildCookie(String cookieName, String value, Duration validDuration) { - return buildCookieInternal(cookieName, value, validDuration); - } - - /** - * Creates a response cookie builder with specified name, value and valid duration. - * - * @param name name of the cookie - * @param value value of the cookie - * @param maxAge valid duration of the cookie - * @return cookie builder - */ - protected ResponseCookie buildCookieInternal( - String name, - String value, - Duration maxAge - ) { - return ResponseCookie.from(name, value) - .secure(cookieManager.getSecure()) - .maxAge(maxAge) - .httpOnly(cookieManager.getHttpOnly()) - .path(cookieManager.getPath()) - .sameSite(cookieManager.getSameSite().attributeValue()) - .build(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java deleted file mode 100644 index fa2118d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.manager.DailyPasswordManager; -import org.springframework.stereotype.Service; - -/** - * Service for generating and caching daily rotation passwords. - * - * @author zihluwang - */ -@Service -public class DailyPasswordService { - - private final DailyPasswordManager dailyPasswordManager; - - public DailyPasswordService(DailyPasswordManager dailyPasswordManager) { - this.dailyPasswordManager = dailyPasswordManager; - } - - /** - * Retrieves the daily password for the current day. - * @return the daily password response - */ - public DailyPasswordResponse getDailyPassword() { - return dailyPasswordManager.getDailyPassword(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java deleted file mode 100644 index 5f5098b..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.FirearmRequest; -import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -/** - * Service handling firearm business logic including CRUD operations and queries. - * - * @author zihluwang - */ -@Service -public class FirearmService { - - private final FirearmRepository firearmRepository; - - public FirearmService(FirearmRepository firearmRepository) { - this.firearmRepository = firearmRepository; - } - - /** - * Queries firearms with optional type filter and pagination. - * - * @param type optional firearm type filter - * @param pageable pagination parameters - * @return a paginated response of firearm records - */ - public PageResponse pageQuery(FirearmType type, Pageable pageable) { - Page page = type == null - ? firearmRepository.findAll(pageable) - : firearmRepository.findAllByType(type, pageable); - - return PageResponse.from(page.map(FirearmResponse::from)); - } - - /** - * Finds a firearm by its ID. - * - * @param id the firearm ID - * @return the firearm response - */ - public FirearmResponse queryById(Long id) { - return firearmRepository.findById(id) - .map(FirearmResponse::from) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - } - - /** - * Creates a new firearm from the provided request data. - * - * @param request the firearm creation request - * @return the created firearm response - */ - public FirearmResponse addFirearm(FirearmRequest request) { - var firearm = firearmRepository.save(Firearm.builder() - .name(request.name()) - .type(request.type()) - .level(request.level()) - .calibre(request.calibre()) - .fireRate(request.fireRate()) - .armourDamage(request.armourDamage()) - .bodyDamage(request.bodyDamage()) - .review(request.review()) - .build()); - - return FirearmResponse.from(firearm); - } - - /** - * Updates an existing firearm identified by ID. - * - * @param id the firearm ID - * @param request the updated firearm data - * @return the updated firearm response - */ - public FirearmResponse updateFirearm(Long id, FirearmRequest request) { - var firearm = firearmRepository.findById(id) - .orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - - firearm.setName(request.name()); - firearm.setType(request.type()); - firearm.setLevel(request.level()); - firearm.setCalibre(request.calibre()); - firearm.setFireRate(request.fireRate()); - firearm.setArmourDamage(request.armourDamage()); - firearm.setBodyDamage(request.bodyDamage()); - firearm.setReview(request.review()); - - return FirearmResponse.from(firearmRepository.save(firearm)); - } - - /** - * Deletes a firearm by its ID. - * - * @param id the firearm ID to delete - */ - public void deleteFirearm(Long id) { - Firearm firearm = firearmRepository.findById(id) - .orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - firearmRepository.delete(firearm); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java deleted file mode 100644 index 9b97e8f..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.domain.dto.TuningRequest; -import com.onixbyte.deltaforceguide.domain.entity.Accessory; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import com.onixbyte.deltaforceguide.domain.entity.Tuning; -import com.onixbyte.deltaforceguide.manager.ModificationManager; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import com.onixbyte.deltaforceguide.repository.ModificationRepository; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Service handling modification business logic including CRUD, batch operations, and tag filtering. - * - * @author zihluwang - */ -@Service -public class ModificationService { - - private final ModificationRepository modificationRepository; - private final FirearmRepository firearmRepository; - private final ModificationManager modificationManager; - private final ObjectMapper objectMapper; - - public ModificationService( - ModificationRepository modificationRepository, - FirearmRepository firearmRepository, - ModificationManager modificationManager, - ObjectMapper objectMapper - ) { - this.modificationRepository = modificationRepository; - this.firearmRepository = firearmRepository; - this.modificationManager = modificationManager; - this.objectMapper = objectMapper; - } - - /** - * Queries modifications with optional firearm and tag filters. - * - * @param firearmId optional firearm ID filter - * @param tags optional tag list filter - * @param pageable pagination parameters - * @return a paginated response of modification records - */ - public PageResponse pageQuery(Long firearmId, List tags, Pageable pageable) { - String tagsJson = null; - if (tags != null && !tags.isEmpty()) { - try { - tagsJson = objectMapper.writeValueAsString(tags); - } catch (JsonProcessingException e) { - throw new RuntimeException("Failed to serialize tags", e); - } - } - - Page page; - if (tagsJson != null || firearmId != null) { - page = modificationRepository.pageQueryByFirearmAndTags(firearmId, tagsJson, pageable); - } else { - page = modificationRepository.findAllBy(pageable); - } - - return PageResponse.from(page.map(ModificationResponse::from)); - } - - /** - * Finds a modification by its ID. - * - * @param id the modification ID - * @return the modification response - */ - public ModificationResponse queryById(Long id) { - return modificationRepository.findById(id) - .map(ModificationResponse::from) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - } - - /** - * Finds all unique tags across modifications, optionally scoped to a firearm. - * - * @param firearmId optional firearm ID to scope the tag search - * @return list of unique tag strings - */ - public List findAllTags(Long firearmId) { - return modificationRepository.findAllTags(firearmId); - } - - /** - * Creates a new modification for a given firearm. - * - * @param request the modification creation request - * @return the created modification response - */ - public ModificationResponse create(ModificationRequest request) { - return modificationManager.create(request); - } - - /** - * Creates multiple modifications in a single batch operation. - * - * @param requests list of modification creation requests - * @return list of created modification responses - */ - public List batchCreate(List requests) { - return modificationManager.batchCreate(requests); - } - - /** - * Updates an existing modification identified by ID. - * - * @param id the modification ID - * @param request the updated modification data - * @return the updated modification response - */ - public ModificationResponse update(Long id, ModificationRequest request) { - Modification modification = modificationRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - Firearm firearm = firearmRepository.findById(request.firearmId()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + request.firearmId())); - - modification.setFirearm(firearm); - modification.setName(request.name()); - modification.setCode(request.code()); - modification.setTags(safeTags(request.tags())); - modification.setAccessories(toAccessories(request.accessories())); - modification.setNote(request.note()); - modification.setAuthor(request.author()); - modification.setVideoUrl(request.videoUrl()); - - return ModificationResponse.from(modificationRepository.save(modification)); - } - - /** - * Deletes a modification by its ID. - * - * @param id the modification ID to delete - */ - public void delete(Long id) { - Modification modification = modificationRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - modificationRepository.delete(modification); - } - - /** - * Deletes multiple modifications in a single batch operation. - * - * @param ids list of modification IDs to delete - */ - public void batchDelete(List ids) { - Set uniqueIds = new LinkedHashSet<>(ids); - List modifications = modificationRepository.findAllById(uniqueIds); - - if (modifications.size() != uniqueIds.size()) { - Set foundIds = modifications.stream() - .map(Modification::getId) - .collect(java.util.stream.Collectors.toSet()); - List missingIds = uniqueIds.stream() - .filter(id -> !foundIds.contains(id)) - .toList(); - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + missingIds); - } - - modificationRepository.deleteAllInBatch(modifications); - } - - private List safeTags(List tags) { - return tags == null ? new ArrayList<>() : tags; - } - - private List toAccessories(List accessoryRequests) { - if (accessoryRequests == null) { - return new ArrayList<>(); - } - - return accessoryRequests.stream() - .map(this::toAccessory) - .toList(); - } - - private Accessory toAccessory(AccessoryRequest request) { - Accessory accessory = new Accessory(); - accessory.setSlotName(request.slotName()); - accessory.setAccessoryName(request.accessoryName()); - accessory.setTunings(toTunings(request.tunings())); - return accessory; - } - - private List toTunings(List tuningRequests) { - if (tuningRequests == null) { - return new ArrayList<>(); - } - - return tuningRequests.stream() - .map(this::toTuning) - .toList(); - } - - private Tuning toTuning(TuningRequest request) { - Tuning tuning = new Tuning(); - tuning.setTuningName(request.tuningName()); - tuning.setTuningValue(request.tuningValue()); - return tuning; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java deleted file mode 100644 index 700e26d..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.manager.UserCredentialManager; -import com.onixbyte.deltaforceguide.manager.UserManager; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.util.List; - -/** - * Service for user account management and profile operations. - * - * @author zihluwang - */ -@Service -public class UserService { - - private final UserManager userManager; - private final UserCredentialManager userCredentialManager; - - public UserService(UserManager userManager, UserCredentialManager userCredentialManager) { - this.userManager = userManager; - this.userCredentialManager = userCredentialManager; - } - - /** - * Retrieves all registered users. - * - * @return list of all users - */ - public List findAll() { - return userManager.findAll(); - } - - /** - * Finds a user by their ID. - * - * @param id the user ID - * @return the user - */ - public User queryById(Long id) { - return userManager.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + id)); - } - - /** - * Finds a user by their username. - * - * @param username the username to search for - * @return the user - */ - public User queryByUsername(String username) { - return userManager.findByUsername(username) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + username)); - } - - /** - * Creates a new user account. - * - * @param user the user entity to persist - * @return the saved user entity - */ - public User create(User user) { - return userManager.save(user); - } - - /** - * Updates an existing user account. - * - * @param user the user entity with updated fields - * @return the saved user entity - */ - public User update(User user) { - if (user.getId() == null || userManager.findById(user.getId()).isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + user.getId()); - } - return userManager.save(user); - } - - /** - * Finds all credentials associated with a user. - * - * @param userId the user ID - * @return list of user credentials - */ - public List findCredentials(Long userId) { - ensureUserExists(userId); - return userCredentialManager.findAllByUserId(userId); - } - - /** - * Queries a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider - * @return the matching credential - */ - public UserCredential queryCredential(Long userId, String provider) { - ensureUserExists(userId); - return userCredentialManager.findByUserIdAndProvider(userId, provider) - .orElseThrow(() -> new ResponseStatusException( - HttpStatus.NOT_FOUND, - "User credential not found: userId=" + userId + ", provider=" + provider)); - } - - /** - * Creates or updates a credential for a user and provider. - * - * @param userId the user ID - * @param provider the authentication provider - * @param credential the credential value - * @return the saved credential - */ - public UserCredential upsertCredential(Long userId, String provider, String credential) { - User user = ensureUserExists(userId); - UserCredential userCredential = userCredentialManager.findByUserIdAndProvider(userId, provider) - .orElseGet(UserCredential::new); - userCredential.setUser(user); - userCredential.setProvider(provider); - userCredential.setCredential(credential); - return userCredentialManager.save(userCredential); - } - - /** - * Deletes a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider - */ - public void deleteCredential(Long userId, String provider) { - ensureUserExists(userId); - userCredentialManager.deleteByUserIdAndProvider(userId, provider); - } - - /** - * Deletes a user and all associated credentials. - * - * @param id the user ID to delete - */ - public void deleteById(Long id) { - ensureUserExists(id); - userCredentialManager.deleteAllByUserId(id); - userManager.deleteById(id); - } - - private User ensureUserExists(Long userId) { - return userManager.findById(userId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + userId)); - } -} - - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java b/server/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java deleted file mode 100644 index e864a77..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.*; -import com.onixbyte.deltaforceguide.manager.ModificationManager; -import com.onixbyte.deltaforceguide.manager.WebhookManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import com.onixbyte.deltaforceguide.exeption.BizException; -import org.yaml.snakeyaml.Yaml; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -@Service -public class WebhookService { - - private static final Logger log = LoggerFactory.getLogger(WebhookService.class); - private static final String TRIGGER_LABEL = "weapon-mod"; - private static final Duration DEDUP_TTL = Duration.ofHours(12); - private static final Pattern YAML_FENCE = - Pattern.compile("```ya?ml\\s*\\R(.*?)```", Pattern.DOTALL); - - private final ModificationManager modificationManager; - private final RedisTemplate redisTemplate; - private final WebhookManager webhookManager; - private final Yaml yaml; - - public WebhookService( - ModificationManager modificationManager, - RedisTemplate redisTemplate, - WebhookManager webhookManager - ) { - this.modificationManager = modificationManager; - this.redisTemplate = redisTemplate; - this.webhookManager = webhookManager; - this.yaml = new Yaml(); - } - - public void processIssueEvent(GitHubIssueRequest request) { - var issue = request.issue(); - var repository = request.repository(); - var sender = request.sender(); - - if (!isAllowedSender(sender)) { - log.info("Issue #{} sender={} not in allowed-users, skipping", - issue.number(), sender != null ? sender.login() : "null"); - return; - } - - if (!hasTriggerLabel(issue.labels())) { - log.debug("Issue #{} lacks trigger label, skipping", issue.number()); - return; - } - - var dedupKey = "github:webhook:processed:%s:%d" - .formatted(repository.fullName(), issue.number()); - var acquired = redisTemplate.opsForValue() - .setIfAbsent(dedupKey, "1", DEDUP_TTL); - if (acquired == null || !acquired) { - log.info("Issue #{} already processed, skipping", issue.number()); - return; - } - - var parsedYaml = extractYaml(issue.body()); - if (parsedYaml == null) { - log.warn("No YAML block found in issue #{} body", issue.number()); - return; - } - - var data = yaml.>load(parsedYaml); - if (data == null) { - log.warn("Empty YAML block in issue #{}", issue.number()); - return; - } - - if (data.containsKey("modifications")) { - processBatch(issue.number(), data); - } else { - processSingle(issue.number(), data); - } - } - - private void processSingle(Long issueNumber, Map data) { - var request = mapToRequest(data); - log.info("Creating modification from issue #{}: name={}", issueNumber, request.name()); - modificationManager.create(request); - } - - @SuppressWarnings("unchecked") - private void processBatch(Long issueNumber, Map data) { - var list = (List>) data.get("modifications"); - if (list == null || list.isEmpty()) { - log.warn("Empty modifications list in issue #{}", issueNumber); - return; - } - var requests = list.stream() - .map(this::mapToRequest) - .toList(); - log.info("Batch creating {} modifications from issue #{}", requests.size(), issueNumber); - modificationManager.batchCreate(requests); - } - - private ModificationRequest mapToRequest(Map data) { - Long firearmId = modificationManager.resolveFirearmId( - toLong(data.get("firearmId")), - (String) data.get("firearmName")); - if (firearmId == null) { - throw new BizException(HttpStatus.BAD_REQUEST, - "YAML must contain firearmId or firearmName"); - } - String name = (String) data.get("name"); - String code = (String) data.get("code"); - List tags = toStringList(data.get("tags")); - List accessories = mapAccessories(data.get("accessories")); - String note = (String) data.get("note"); - String author = (String) data.get("author"); - String videoUrl = (String) data.get("videoUrl"); - - return new ModificationRequest(firearmId, name, code, tags, accessories, - note, author, videoUrl); - } - - private List mapAccessories(Object raw) { - if (!(raw instanceof List list)) { - return new ArrayList<>(); - } - var result = new ArrayList(); - for (var item : list) { - if (item instanceof Map map) { - result.add(new AccessoryRequest( - (String) map.get("slotName"), - (String) map.get("accessoryName"), - mapTunings(map.get("tunings")) - )); - } - } - return result; - } - - private List mapTunings(Object raw) { - if (!(raw instanceof List list)) { - return new ArrayList<>(); - } - var result = new ArrayList(); - for (var item : list) { - if (item instanceof Map map) { - result.add(new TuningRequest( - (String) map.get("tuningName"), - toDouble(map.get("tuningValue")) - )); - } - } - return result; - } - - private List toStringList(Object raw) { - if (raw instanceof List list) { - return list.stream() - .filter(String.class::isInstance) - .map(String.class::cast) - .toList(); - } - return new ArrayList<>(); - } - - private boolean isAllowedSender( - GitHubWebhookSender sender - ) { - var allowedUsers = webhookManager.allowedUsers(); - if (allowedUsers == null || allowedUsers.isEmpty()) { - return true; - } - if (sender == null || sender.login() == null) { - return false; - } - return allowedUsers.contains(sender.login()); - } - - private boolean hasTriggerLabel(List labels) { - if (labels == null) { - return false; - } - return labels.stream().anyMatch(label -> TRIGGER_LABEL.equals(label.name())); - } - - private String extractYaml(String body) { - if (body == null) { - return null; - } - var matcher = YAML_FENCE.matcher(body); - return matcher.find() ? matcher.group(1) : null; - } - - private Long toLong(Object value) { - if (value instanceof Number num) { - return num.longValue(); - } - if (value instanceof String s) { - return Long.parseLong(s); - } - return null; - } - - private Double toDouble(Object value) { - if (value instanceof Number num) { - return num.doubleValue(); - } - if (value instanceof String s) { - return Double.parseDouble(s); - } - return null; - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java deleted file mode 100644 index 26f0112..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * Constants for HTTP cookie names used for authentication tokens. - * - * @author zihluwang - */ -public class CookieName { - - public static final String ACCESS_TOKEN = "AccessToken"; -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java deleted file mode 100644 index 9488536..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * Constants for supported authentication provider identifiers. - * - * @author zihluwang - */ -public class CredentialProvider { - - public static final String LOCAL = "LOCAL"; -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java deleted file mode 100644 index 134408b..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import java.time.format.DateTimeFormatter; - -/** - * Utility class providing predefined {@link DateTimeFormatter} instances for common date and - * time patterns. These formatters can be used to parse and format {@code java.time} objects - * consistently throughout an application. - * - * @author zihluwang - */ -public class DateTimeFormatters { - - /** - * A {@link DateTimeFormatter} for formatting and parsing full date and time with seconds, using - * the pattern "yyyy-MM-dd HH:mm:ss". - *

- * Example: "{@code 2023-10-27 15:30:45}" - */ - public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing dates only, using the - * pattern "yyyy-MM-dd". - *

- * Example: "{@code 2023-10-27}" - */ - public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing times only, using the - * pattern "HH:mm:ss". - *

- * Example: "{@code 15:30:45}" - */ - public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing year and month only, using the - * pattern "yyyy-MM". - *

- * Example: "{@code 2023-10}" - */ - public static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java deleted file mode 100644 index 05b9d58..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * This class lists the header names that GitHub sends in webhook requests. - * - * @author siujamo - */ -public class GitHubWebhookHeader { - - /** - * The unique identifier of the webhook. - */ - public static final String HOOK_ID = "X-GitHub-Hook-ID"; - - /** - * The name of the event that triggered the delivery. - */ - public static final String EVENT = "X-GitHub-Event"; - - /** - * A globally unique identifier (GUID) to identify the event. - */ - public static final String DELIVERY = "X-GitHub-Delivery"; - - /** - * This header is sent if the webhook is configured with a {@code secret}. This is the HMAC hex - * digest of the request body, and is generated using the SHA-1 hash function and the secret as - * the HMAC {@code key}. {@code X-Hub-Signature} is provided for compatibility with - * existing integrations. We recommend that you use the more secure - * {@code X-Hub-Signature-256} instead. - */ - public static final String SIGNATURE = "X-Hub-Signature"; - - /** - * This header is sent if the webhook is configured with a {@code secret}. This is the HMAC hex - * digest of the request body, and is generated using the SHA-256 hash function and the - * {@code secret} as the HMAC key. For more information, see Validating webhook deliveries. - */ - public static final String SIGNATURE_256 = "X-Hub-Signature-256"; - - /** - * This header will always have the prefix {@code GitHub-Hookshot/}. - */ - public static final String USER_AGENT = "User-Agent"; - - /** - * The type of resource where the webhook was created. - */ - public static final String INSTALLATION_TARGET_TYPE = "X-GitHub-Hook-Installation-Target-Type"; - - /** - * The unique identifier of the resource where the webhook was created. - */ - public static final String INSTALLATION_TARGET_ID = "X-GitHub-Hook-Installation-Target-ID"; -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java deleted file mode 100644 index ae47823..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - -/** - * Shared Jackson {@link com.fasterxml.jackson.databind.Module} instances for custom date/time - * serialisation and deserialisation across the application. - * - * @author zihluwang - */ -public class JacksonModules { - - public static final SimpleModule DATE_TIME_MODULE = initialiseDateTimeModule(); - - private static SimpleModule initialiseDateTimeModule() { - var javaTimeModule = new JavaTimeModule(); - javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatters.DATE_TIME_FORMATTER)); - javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatters.DATE_TIME_FORMATTER)); - - javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatters.DATE_FORMATTER)); - javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatters.DATE_FORMATTER)); - - javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatters.TIME_FORMATTER)); - javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatters.TIME_FORMATTER)); - - return javaTimeModule; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java b/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java deleted file mode 100644 index 4e50c38..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; - -/** - * Singleton serialiser for Redis cache operations using - * {@link GenericJackson2JsonRedisSerializer} with JavaTime module support. - * - * @author zihluwang - */ -public class JacksonRedisSerialiser { - - public static final GenericJackson2JsonRedisSerializer INSTANCE = initialiseSerializer(); - - private static GenericJackson2JsonRedisSerializer initialiseSerializer() { - var serializer = new GenericJackson2JsonRedisSerializer(); - - serializer.configure((configurer) -> { - configurer.registerModule(new JavaTimeModule()); - configurer.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - }); - - return serializer; - } -} - diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java b/server/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java deleted file mode 100644 index 1d576a5..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.utils; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * Utility class for date and time operations using system-default time zone. - * - * @author zihluwang - */ -public class DateTimeUtil { - - /** - * Convert a {@link LocalDateTime} to an {@link Instant} using the system-default time zone. - * - * @param ldt the local date-time to convert - * @return the corresponding instant - */ - public static Instant asInstant(LocalDateTime ldt) { - return ldt.atZone(ZoneId.systemDefault()) - .toInstant(); - } -} diff --git a/server/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java b/server/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java deleted file mode 100644 index ee31c22..0000000 --- a/server/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.onixbyte.deltaforceguide.wrapper; - -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import org.springframework.util.StreamUtils; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper { - - private final byte[] body; - - public RepeatedlyReadRequestWrapper(HttpServletRequest request) throws IOException { - super(request); - this.body = StreamUtils.copyToByteArray(request.getInputStream()); - } - - @Override - public ServletInputStream getInputStream() { - final var byteArrayInputStream = new ByteArrayInputStream(body); - return new ServletInputStream() { - @Override - public boolean isFinished() { - return byteArrayInputStream.available() == 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) { - } - - @Override - public int read() throws IOException { - return byteArrayInputStream.read(); - } - }; - } - - @Override - public BufferedReader getReader() throws IOException { - return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8)); - } - - public String getBodyString() { - return new String(body, StandardCharsets.UTF_8); - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 2b10ea5..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - mavenCentral() - } -} - -dependencyResolutionManagement { - repositories { - mavenCentral() - } -} - -rootProject.name = "delta-force-guide-server" diff --git a/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java b/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java deleted file mode 100644 index a23fb88..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** - * Entry point for the Delta Force Guide Server application. - * - * @author zihluwang - */ -@SpringBootApplication -public class DeltaForceGuideApplication { - - public static void main(String[] args) { - SpringApplication.run(DeltaForceGuideApplication.class, args); - } - -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java b/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java deleted file mode 100644 index a7ad3f1..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.onixbyte.deltaforceguide.client; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.properties.TokenProperties; -import com.onixbyte.deltaforceguide.utils.DateTimeUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; - -/** - * Client for generating and verifying JSON Web Tokens using the Auth0 java-jwt library. - * - * @author zihluwang - */ -@Component -public class TokenClient { - - private final Algorithm algorithm; - private final TokenProperties tokenProperties; - private final JWTVerifier verifier; - - /** - * Constructs a new TokenClient with the necessary algorithm and token properties. - * - * @param algorithm the signing algorithm used to secure the JWT - * @param tokenProperties the configuration properties for the token, such as issuer and - * validity period - */ - @Autowired - public TokenClient( - Algorithm algorithm, - TokenProperties tokenProperties, - JWTVerifier verifier - ) { - this.algorithm = algorithm; - this.tokenProperties = tokenProperties; - this.verifier = verifier; - } - - /** - * Generate a JSON Web Token to the current user. - * - * @param user the current user for whom the token is being generated - * @return a JWT string - */ - public String generateToken(User user) { - var issuedAt = LocalDateTime.now(); - var expiresAt = issuedAt.plus(tokenProperties.validTime()); - - return JWT.create() - .withSubject(user.getUsername()) - .withIssuer(tokenProperties.issuer()) - .withIssuedAt(DateTimeUtil.asInstant(issuedAt)) - .withExpiresAt(DateTimeUtil.asInstant(expiresAt)) - .sign(algorithm); - } - - /** - * Verify and decode token. - * - * @param token a JWT token - * @return information included in the given token - * @throws com.auth0.jwt.exceptions.JWTVerificationException if the token is invalid, such as - * expired, or not signed by - * specific server - */ - public DecodedJWT verifyToken(String token) { - return verifier.verify(token); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java deleted file mode 100644 index 6bba2db..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.interceptor.GitHubWebhookInterceptor; -import com.onixbyte.deltaforceguide.interceptor.TrafficInterceptor; -import com.onixbyte.deltaforceguide.properties.AppProperties; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@EnableConfigurationProperties(AppProperties.class) -public class AppConfig implements WebMvcConfigurer { - - private final TrafficInterceptor trafficInterceptor; - - private final GitHubWebhookInterceptor gitHubWebhookInterceptor; - - @Autowired - public AppConfig( - TrafficInterceptor trafficInterceptor, - GitHubWebhookInterceptor gitHubWebhookInterceptor - ) { - this.trafficInterceptor = trafficInterceptor; - this.gitHubWebhookInterceptor = gitHubWebhookInterceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(trafficInterceptor); - registry.addInterceptor(gitHubWebhookInterceptor) - .addPathPatterns("/webhooks/github"); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java deleted file mode 100644 index 0502c4a..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.shared.JacksonRedisSerialiser; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.RedisSerializer; - -import java.time.Duration; - -/** - * Configuration class for Redis-based caching components. - *

- * This configuration class provides beans for Redis cache management and template operations - * within the Helix application. It configures custom serialisation strategies using - * {@link GenericJackson2JsonRedisSerializer} for values and string serialisation for keys, - * ensuring optimal performance and compatibility with JSON-based data structures. - *

- * The configuration includes: - *

- * - * @author zihluwang - * @see RedisCacheManager - * @see RedisTemplate - * @see GenericJackson2JsonRedisSerializer - * @since 1.0.0 - */ -@Configuration -@EnableCaching -public class CacheConfig { - - /** - * Creates a custom Redis cache manager with JSON serialisation support. - *

- * This method configures a {@link RedisCacheManager} that uses string serialisation for cache - * keys and {@link GenericJackson2JsonRedisSerializer} for cache values. This setup ensures that - * complex objects can be stored and retrieved from Redis cache whilst maintaining readability - * and compatibility with JSON-based systems. - * - * @param connectionFactory the Redis connection factory used to establish connections - * @return a configured {@link RedisCacheManager} with custom serialisation settings - * @see RedisCacheManager - * @see GenericJackson2JsonRedisSerializer - * @see RedisSerializationContext - */ - @Bean - public RedisCacheManager cacheManager( - RedisConnectionFactory connectionFactory - ) { - var _keySerializer = RedisSerializer.string(); - - var cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() - .serializeKeysWith(RedisSerializationContext.SerializationPair - .fromSerializer(_keySerializer)) - .serializeValuesWith(RedisSerializationContext.SerializationPair - .fromSerializer(JacksonRedisSerialiser.INSTANCE)) - .entryTtl(Duration.ofMinutes(90L)); - - return RedisCacheManager.RedisCacheManagerBuilder - .fromConnectionFactory(connectionFactory) - .cacheDefaults(cacheConfiguration) - .build(); - } - - /** - * Creates a Redis template for direct Redis operations with custom serialisation. - *

- * This method configures a {@link RedisTemplate} that uses string serialisation for keys - * and {@link GenericJackson2JsonRedisSerializer} for values. This template provides low-level - * access to Redis operations whilst ensuring consistent serialisation strategies across - * the application. - *

- * The template is fully configured and ready for use after bean creation. - * - * @param connectionFactory the Redis connection factory used to establish connections - * @return a fully configured {@link RedisTemplate} for Redis operations - * @see RedisTemplate - * @see GenericJackson2JsonRedisSerializer - * @see RedisSerializer - */ - @Bean - public RedisTemplate redisTemplate( - RedisConnectionFactory connectionFactory - ) { - var redisTemplate = new RedisTemplate(); - redisTemplate.setConnectionFactory(connectionFactory); - redisTemplate.setKeySerializer(RedisSerializer.string()); - redisTemplate.setValueSerializer(JacksonRedisSerialiser.INSTANCE); - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java deleted file mode 100644 index 1e13243..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.properties.CorsProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; -import java.util.stream.Stream; - -/** - * Configuration for CORS (Cross-Origin Resource Sharing) policies. - * - * @author zihluwang - */ -@Configuration -@EnableConfigurationProperties({CorsProperties.class}) -public class CorsConfig { - - /** - * Creates the CORS configuration source with properties from configuration. - * - * @param properties the CORS configuration properties - * @return the CORS configuration source - */ - @Bean - public CorsConfigurationSource corsConfigurationSource( - CorsProperties properties - ) { - var corsConfiguration = new CorsConfiguration(); - corsConfiguration.setAllowCredentials(properties.allowCredentials()); - corsConfiguration.setAllowedOrigins(List.of(properties.allowedOrigins())); - corsConfiguration.setAllowedHeaders(List.of(properties.allowedHeaders())); - corsConfiguration.setAllowedMethods(Stream.of(properties.allowedMethods()) - .map(HttpMethod::name) - .toList()); - corsConfiguration.setMaxAge(properties.maxAge()); - corsConfiguration.setAllowPrivateNetwork(properties.allowPrivateNetwork()); - corsConfiguration.setExposedHeaders(List.of(properties.exposedHeaders())); - - var corsConfigurationSource = new UrlBasedCorsConfigurationSource(); - corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); - return corsConfigurationSource; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java deleted file mode 100644 index 893852a..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.filter.WebhookFilter; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class FilterConfig { - - @Bean - public FilterRegistrationBean webhookFilterBean(WebhookFilter webhookFilter) { - var registrationBean = new FilterRegistrationBean(); - - registrationBean.setFilter(webhookFilter); - registrationBean.addUrlPatterns("/webhooks/*"); - registrationBean.setOrder(1); - - return registrationBean; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java deleted file mode 100644 index 895bcd1..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.onixbyte.deltaforceguide.shared.JacksonModules; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for Jackson JSON serialisation and deserialisation settings. - * - * @author zihluwang - */ -@Configuration -public class JacksonConfig { - - @Bean - public Jackson2ObjectMapperBuilderCustomizer jacksonCustomiser() { - return (builder) -> { - builder.modules(JacksonModules.DATE_TIME_MODULE); - builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - }; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java deleted file mode 100644 index 0e2ae74..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for MyBatis SQL mapping framework integration. - * - * @author zihluwang - */ -@Configuration -@MapperScan(basePackages = {"com.onixbyte.deltaforceguide.mapper"}) -public class MyBatisConfig { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java b/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java deleted file mode 100644 index 9f47fe1..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.info.Contact; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.info.License; -import org.springframework.context.annotation.Configuration; - -@OpenAPIDefinition( - info = @Info( - title = "Delta Force Guide Server", - description = "API for managing Delta Force game firearm builds", - version = "1.3.4", - contact = @Contact( - name = "Zihlu Wang", - email = "zihlu.wang@onixbyte.com" - ), - license = @License( - name = "MIT", - url = "https://git.onixbyte.cn/onixbyte/delta-force-guide-server/-/raw/main/LICENCE" - ) - ) -) -@Configuration -public class OpenApiConfiguration { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java deleted file mode 100644 index b32a691..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.algorithms.Algorithm; -import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter; -import com.onixbyte.deltaforceguide.properties.CookieProperties; -import com.onixbyte.deltaforceguide.properties.TokenProperties; -import com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -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.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.web.cors.CorsConfigurationSource; - -/** - * Spring Security configuration defining authentication, authorisation, and filter chains. - * - * @author zihluwang - */ -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -@EnableConfigurationProperties({TokenProperties.class, CookieProperties.class}) -public class SecurityConfig { - - /** - * Configures the HTTP security filter chain including endpoint authorisation and JWT filter. - * - * @param http the HTTP security builder - * @return the configured security filter chain - */ - @Bean - public SecurityFilterChain securityFilterChain( - HttpSecurity httpSecurity, - CorsConfigurationSource corsConfigurationSource, - TokenAuthenticationFilter tokenAuthenticationFilter - ) throws Exception { - return httpSecurity - .cors((cors) -> cors - .configurationSource(corsConfigurationSource)) - .csrf(AbstractHttpConfigurer::disable) - .sessionManagement((customiser) -> customiser - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests((customiser) -> customiser - .anyRequest().permitAll() - ) - .addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class) - .build(); - } - - /** - * Provides the BCrypt password encoder for credential hashing. - * @return the BCrypt password encoder - */ - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** - * Provides the authentication manager for the security configuration. - * - * @return the authentication manager - */ - @Bean - public AuthenticationManager authenticationManager( - UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider - ) { - return new ProviderManager( - usernamePasswordAuthenticationProvider - ); - } - - @Bean - public Algorithm algorithm(TokenProperties properties) { - return Algorithm.HMAC256(properties.secret()); - } - - @Bean - public JWTVerifier verifier(Algorithm algorithm, TokenProperties tokenProperties) { - return JWT.require(algorithm) - .withIssuer(tokenProperties.issuer()) - .build(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java deleted file mode 100644 index 1dfe3ff..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -/** - * Configuration for Spring Data JPA auditing and repository settings. - * - * @author zihluwang - */ -@Configuration -@EnableJpaRepositories(basePackages = {"com.onixbyte.deltaforceguide.repository"}) -public class SpringDataConfig { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java deleted file mode 100644 index d9c5db7..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onixbyte.deltaforceguide.config; - -import com.onixbyte.deltaforceguide.properties.GitHubWebhookProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({GitHubWebhookProperties.class}) -public class WebhookConfig { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java deleted file mode 100644 index db0f42e..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.LoginRequest; -import com.onixbyte.deltaforceguide.domain.dto.UserResponse; -import com.onixbyte.deltaforceguide.client.TokenClient; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.AuthService; -import com.onixbyte.deltaforceguide.service.CookieService; -import com.onixbyte.deltaforceguide.shared.CookieName; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.time.Duration; -import java.time.LocalDateTime; - -/** - * REST controller for user authentication endpoints (login, logout). - * - * @author zihluwang - */ -@Tag(name = "用户鉴权", description = "处理用户登录与退出功能") -@RestController -@RequestMapping("/auth") -public class AuthController { - - private final AuthService authService; - private final TokenClient tokenClient; - private final CookieService cookieService; - - public AuthController(AuthService authService, TokenClient tokenClient, CookieService cookieService) { - this.authService = authService; - this.tokenClient = tokenClient; - this.cookieService = cookieService; - } - - @Operation(description = "用户登录") - @PostMapping("/login") - public ResponseEntity login(@Validated @RequestBody LoginRequest request) { - var user = authService.login(request); - var currentTime = LocalDateTime.now(); - var accessToken = tokenClient.generateToken(user); - var accessTokenCookie = cookieService.buildCookie(CookieName.ACCESS_TOKEN, accessToken); - var cookieMaxAge = accessTokenCookie.getMaxAge(); - - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()) - .body(UserResponse.from(user, currentTime.plus(cookieMaxAge))); - } - - @RequiresAuth - @Operation(description = "退出登录") - @PostMapping("/logout") - public ResponseEntity logout() { - var expiredCookie = cookieService.buildCookie(CookieName.ACCESS_TOKEN, "", Duration.ZERO); - return ResponseEntity.noContent() - .header(HttpHeaders.SET_COOKIE, expiredCookie.toString()) - .build(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java deleted file mode 100644 index 527e730..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.service.DailyPasswordService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * REST controller for retrieving daily-generated passwords. - * - * @author zihluwang - */ -@Tag(name = "每日密码", description = "获取每日密码信息") -@RestController -@RequestMapping("/daily-passwords") -public class DailyPasswordController { - - private final DailyPasswordService dailyPasswordService; - - public DailyPasswordController(DailyPasswordService dailyPasswordService) { - this.dailyPasswordService = dailyPasswordService; - } - - @Operation(description = "获取当日的每日密码数据,该数据将被缓存一天") - @GetMapping - public DailyPasswordResponse getDailyPassword() { - return dailyPasswordService.getDailyPassword(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java deleted file mode 100644 index 4843c2d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.FirearmRequest; -import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.FirearmService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -/** - * REST controller for firearm CRUD operations. - * - * @author zihluwang - */ -@Tag(name = "武器管理", description = "与武器有关的操作") -@RestController -@RequestMapping("/firearms") -public class FirearmController { - - private final FirearmService firearmService; - - public FirearmController(FirearmService firearmService) { - this.firearmService = firearmService; - } - - @Operation(description = "获取分页武器数据") - @Validated - @GetMapping - public PageResponse pageQuery( - @RequestParam(defaultValue = "0") @Min(0) int page, - @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, - @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "DESC") Sort.Direction direction, - @RequestParam(required = false) FirearmType type - ) { - return firearmService.pageQuery(type, PageRequest.of(page, size, Sort.by(direction, sortBy))); - } - - @Operation(description = "获取指定武器的数据") - @GetMapping("/{id}") - public FirearmResponse queryById(@PathVariable Long id) { - return firearmService.queryById(id); - } - - @RequiresAuth - @PostMapping - public FirearmResponse addFirearm(@Validated @RequestBody FirearmRequest request) { - return firearmService.addFirearm(request); - } - - @RequiresAuth - @Operation(description = "更新指定武器的数据") - @PutMapping("/{id}") - public FirearmResponse updateFirearm(@PathVariable Long id, @Validated @RequestBody FirearmRequest request) { - return firearmService.updateFirearm(id, request); - } - - @RequiresAuth - @Operation(description = "删除指定武器的数据") - @DeleteMapping("/{id}") - public void deleteFirearm(@PathVariable Long id) { - firearmService.deleteFirearm(id); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java deleted file mode 100644 index 580e1f9..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.GitHubIssueRequest; -import com.onixbyte.deltaforceguide.service.WebhookService; -import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/webhooks/github") -public class GitHubWebhookController { - - private static final Logger log = LoggerFactory.getLogger(GitHubWebhookController.class); - - private final WebhookService webhookService; - - public GitHubWebhookController(WebhookService webhookService) { - this.webhookService = webhookService; - } - - @PostMapping - public ResponseEntity handleWebhook( - @RequestHeader(GitHubWebhookHeader.EVENT) String event, - @RequestBody GitHubIssueRequest request - ) { - if (!"issues".equals(event)) { - log.debug("Ignoring non-issue event: {}", event); - return ResponseEntity.ok().build(); - } - if (!"opened".equals(request.action())) { - log.debug("Ignoring issue action: {}", request.action()); - return ResponseEntity.ok().build(); - } - webhookService.processIssueEvent(request); - return ResponseEntity.ok().build(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java b/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java deleted file mode 100644 index 4edd154..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.ErrorResponse; -import com.onixbyte.deltaforceguide.exeption.BizException; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -/** - * Global exception handler that translates exceptions into standard error responses. - * - * @author zihluwang - */ -@RestControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(BizException.class) - public ResponseEntity handleBizException(BizException exception) { - var status = exception.getStatus(); - return ResponseEntity.status(status) - .body(new ErrorResponse(exception.getMessage())); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java deleted file mode 100644 index 7eaf94c..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.domain.dto.ModificationBatchCreateRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth; -import com.onixbyte.deltaforceguide.service.ModificationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.Positive; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * REST controller for modification CRUD operations, including batch creation and deletion. - * - * @author zihluwang - */ -@Tag(name = "改装管理", description = "对枪械改装的管理") -@RestController -@RequestMapping("/modifications") -public class ModificationController { - - private final ModificationService modificationService; - - public ModificationController(ModificationService modificationService) { - this.modificationService = modificationService; - } - - @Operation(description = "分页查询改装信息") - @Validated - @GetMapping - public PageResponse pageQuery( - @RequestParam(defaultValue = "0") @Min(0) int page, - @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, - @RequestParam(required = false) @Positive Long firearmId, - @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "DESC") Sort.Direction direction, - @RequestParam(required = false) List tags - ) { - return modificationService.pageQuery(firearmId, tags, PageRequest.of(page, size, Sort.by(direction, sortBy))); - } - - @Operation(description = "查询指定改装的信息") - @GetMapping("/{id}") - public ModificationResponse queryById(@PathVariable Long id) { - return modificationService.queryById(id); - } - - @RequiresAuth - @Operation(description = "创建改装") - @PostMapping - public ModificationResponse create(@Valid @RequestBody ModificationRequest request) { - return modificationService.create(request); - } - - @RequiresAuth - @Operation(description = "批量创建改装") - @PostMapping("/batch") - public List batchCreate(@Valid @RequestBody ModificationBatchCreateRequest request) { - return modificationService.batchCreate(request.modifications()); - } - - @RequiresAuth - @Operation(description = "修改指定改装") - @PutMapping("/{id}") - public ModificationResponse update(@PathVariable Long id, @Valid @RequestBody ModificationRequest request) { - return modificationService.update(id, request); - } - - @RequiresAuth - @Operation(description = "删除指定改装") - @DeleteMapping("/{id}") - public void delete(@PathVariable Long id) { - modificationService.delete(id); - } - - @RequiresAuth - @Operation(description = "批量删除改装") - @DeleteMapping("/batch-delete") - @Validated - public void batchDelete(@RequestParam List<@Positive Long> ids) { - modificationService.batchDelete(ids); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java deleted file mode 100644 index b9f4e59..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import com.onixbyte.deltaforceguide.service.ModificationService; - -import java.util.List; - -/** - * REST controller for retrieving available modification tags. - * - * @author zihluwang - */ -@Tag(name = "标签管理", description = "管理标签信息") -@RestController -@RequestMapping("/tags") -public class TagController { - - private final ModificationService modificationService; - - public TagController(ModificationService modificationService) { - this.modificationService = modificationService; - } - - @Operation(description = "查询指定武器或所有武器的标签") - @GetMapping - public List getTags(@RequestParam(required = false) Long firearmId) { - return modificationService.findAllTags(firearmId); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java deleted file mode 100644 index 67a08ef..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.controller; - -import com.onixbyte.deltaforceguide.service.AppService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "版本信息") -@RestController -@RequestMapping("/versions") -public class VersionController { - - private final AppService appService; - - public VersionController(AppService appService) { - this.appService = appService; - } - - @Operation(description = "获取当前应用版本号") - @GetMapping - public String getVersion() { - return appService.getVersion(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java b/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java deleted file mode 100644 index 8108db3..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.converter; - -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; - -/** - * JPA attribute converter that maps {@link FirearmType} enum to/from its integer database representation. - * - * @author zihluwang - */ -@Converter -public class FirearmTypeConverter implements AttributeConverter { - - @Override - public Integer convertToDatabaseColumn(FirearmType attribute) { - return attribute == null ? null : attribute.getCode(); - } - - @Override - public FirearmType convertToEntityAttribute(Integer dbData) { - return FirearmType.fromCode(dbData); - } -} - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java deleted file mode 100644 index b94b61c..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; - -import java.util.ArrayList; -import java.util.List; - -/** - * Request DTO for creating or updating an accessory attached to a modification. - * - * @author zihluwang - */ -public record AccessoryRequest( - @NotBlank(message = "插槽名称不能为空") - String slotName, - @NotBlank(message = "配件名称不能为空") - String accessoryName, - List<@Valid TuningRequest> tunings -) { - public List tunings() { - return tunings == null ? new ArrayList<>() : tunings; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java deleted file mode 100644 index ade3eac..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Accessory; - -import java.util.List; - -/** - * Response DTO for an accessory attached to a modification. - * - * @author zihluwang - */ -public record AccessoryResponse( - String slotName, - String accessoryName, - List tunings -) { - public static AccessoryResponse from(Accessory accessory) { - return new AccessoryResponse( - accessory.getSlotName(), - accessory.getAccessoryName(), - accessory.getTunings() == null - ? List.of() - : accessory.getTunings().stream().map(TuningResponse::from).toList() - ); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java deleted file mode 100644 index edfa9c0..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * DTO representing a single daily-generated password for a map. - * - * @author zihluwang - */ -public record DailyPassword( - String mapName, - String password -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java deleted file mode 100644 index 5e3c12f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * DTO containing daily password data including update information and password list. - * - * @author zihluwang - */ -public record DailyPasswordData( - String updateDate, - Integer totalCount, - List passwords, - String source, - LocalDateTime lastUpdated, - Long timestamp -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java deleted file mode 100644 index 9af994f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * DTO holding metadata about the daily password source and update tracking. - * - * @author zihluwang - */ -public record DailyPasswordMetadata( - String version, - String author -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java deleted file mode 100644 index d3ce808..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * Response DTO wrapping daily password data with metadata. - * - * @author zihluwang - */ -public record DailyPasswordResponse( - String status, - String message, - DailyPasswordData data, - DailyPasswordMetadata metadata -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java deleted file mode 100644 index 11879fc..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -/** - * Standard error response body returned on API failures. - * - * @author zihluwang - */ -public record ErrorResponse( - String message -) { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java deleted file mode 100644 index 02fb855..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.enumeration.FirearmType; - -/** - * Request DTO for creating or updating a firearm. - * - * @author zihluwang - */ -public record FirearmRequest( - String name, - FirearmType type, - String level, - String calibre, - Integer fireRate, - Integer armourDamage, - Integer bodyDamage, - String review -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java deleted file mode 100644 index 0e9eaba..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; - -/** - * Response DTO for a firearm record, including associated modifications. - * - * @author zihluwang - */ -public record FirearmResponse( - Long id, - String name, - FirearmType type, - String level, - String calibre, - Integer fireRate, - Integer armourDamage, - Integer bodyDamage, - String review -) { - public static FirearmResponse from(Firearm firearm) { - return new FirearmResponse( - firearm.getId(), - firearm.getName(), - firearm.getType(), - firearm.getLevel(), - firearm.getCalibre(), - firearm.getFireRate(), - firearm.getArmourDamage(), - firearm.getBodyDamage(), - firearm.getReview() - ); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java deleted file mode 100644 index 8c4e1a6..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubIssueRequest( - String action, - GitHubWebhookIssue issue, - GitHubWebhookRepository repository, - GitHubWebhookSender sender -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java deleted file mode 100644 index 91263cc..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import java.util.List; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookIssue( - String url, - Long id, - Long number, - String title, - String body, - List labels -) { - -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java deleted file mode 100644 index 33954ab..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookLabel( - String name -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java deleted file mode 100644 index e3858d6..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookRepository( - String fullName -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java deleted file mode 100644 index 416891e..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@JsonIgnoreProperties(ignoreUnknown = true) -public record GitHubWebhookSender( - String login -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java deleted file mode 100644 index 106d533..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -/** - * Login request containing principle (username/email) and credential (password). - * - * @author zihluwang - */ -@Schema(description = "登录请求") -public record LoginRequest( - @NotBlank(message = "登录名称不能为空") - @Schema(description = "用户名或电子邮箱", requiredMode = Schema.RequiredMode.REQUIRED) - String principle, - @NotBlank(message = "登录口令不能为空") - @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED) - String credential -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java deleted file mode 100644 index c0aa59f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; - -import java.util.List; - -/** - * Request DTO for batch creation of modifications. - * - * @author zihluwang - */ -public record ModificationBatchCreateRequest( - @NotEmpty(message = "批量创建列表不能为空") - List<@Valid ModificationRequest> modifications -) { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java deleted file mode 100644 index 4243895..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Positive; - -import java.util.List; - -/** - * Request DTO for batch deletion of modifications by ID. - * - * @author zihluwang - */ -public record ModificationBatchDeleteRequest( - @NotEmpty(message = "批量删除ID列表不能为空") - List<@Positive(message = "ID必须为正数") Long> ids -) { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java deleted file mode 100644 index e4f6708..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - -import java.util.ArrayList; -import java.util.List; - -/** - * Request DTO for creating or updating a modification. - * - * @author zihluwang - */ -public record ModificationRequest( - @NotNull(message = "武器ID不能为空") - @Positive(message = "武器ID必须为正数") - Long firearmId, - @NotBlank(message = "改装名称不能为空") - String name, - @NotBlank(message = "改装代码不能为空") - String code, - List<@NotBlank(message = "标签不能为空") String> tags, - List<@Valid AccessoryRequest> accessories, - String note, - String author, - String videoUrl -) { - public List tags() { - return tags == null ? new ArrayList<>() : tags; - } - - public List accessories() { - return accessories == null ? new ArrayList<>() : accessories; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java deleted file mode 100644 index aca6ef5..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Modification; - -import java.util.List; - -/** - * Response DTO for a modification record including accessories and tags. - * - * @author zihluwang - */ -public record ModificationResponse( - Long id, - Long firearmId, - String name, - String code, - List tags, - List accessories, - String note, - String author, - String videoUrl -) { - public static ModificationResponse from(Modification modification) { - return new ModificationResponse( - modification.getId(), - modification.getFirearm().getId(), - modification.getName(), - modification.getCode(), - modification.getTags(), - modification.getAccessories() == null - ? List.of() - : modification.getAccessories().stream().map(AccessoryResponse::from).toList(), - modification.getNote(), - modification.getAuthor(), - modification.getVideoUrl() - ); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java deleted file mode 100644 index 20738bd..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import org.springframework.data.domain.Page; - -import java.util.List; - -/** - * Generic paginated response wrapper for list endpoints. - * - * @author zihluwang - */ -public record PageResponse( - List items, - int page, - int size, - long totalElements, - int totalPages -) { - public static PageResponse from(Page source) { - return new PageResponse<>( - source.getContent(), - source.getNumber(), - source.getSize(), - source.getTotalElements(), - source.getTotalPages() - ); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java deleted file mode 100644 index a180eb3..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * Request DTO for a tuning adjustment on an accessory. - * - * @author zihluwang - */ -public record TuningRequest( - @NotBlank(message = "调校项名称不能为空") - String tuningName, - @NotNull(message = "调校值不能为空") - Double tuningValue -) { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java deleted file mode 100644 index 405ecde..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.Tuning; - -/** - * Response DTO for a tuning adjustment on an accessory. - * - * @author zihluwang - */ -public record TuningResponse( - String tuningName, - Double tuningValue -) { - public static TuningResponse from(Tuning tuning) { - return new TuningResponse( - tuning.getTuningName(), - tuning.getTuningValue() - ); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java deleted file mode 100644 index 4a2d82d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.dto; - -import com.onixbyte.deltaforceguide.domain.entity.User; - -import java.time.LocalDateTime; - -/** - * Response DTO for a user account, including associated credentials. - * - * @author zihluwang - */ -public record UserResponse( - Long id, - String username, - String email, - LocalDateTime expiration -) { - public static UserResponse from(User user, LocalDateTime expiration) { - return new UserResponse( - user.getId(), - user.getUsername(), - user.getEmail(), - expiration - ); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java deleted file mode 100644 index cde914d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Entity representing an accessory attached to a modification, stored as JSONB. - * - * @author zihluwang - */ -public class Accessory { - - private String slotName; - - private String accessoryName; - - private List tunings = new ArrayList<>(); - - public Accessory() { - } - - public String getSlotName() { - return slotName; - } - - public void setSlotName(String slotName) { - this.slotName = slotName; - } - - public String getAccessoryName() { - return accessoryName; - } - - public void setAccessoryName(String accessoryName) { - this.accessoryName = accessoryName; - } - - public List getTunings() { - return tunings; - } - - public void setTunings(List tunings) { - this.tunings = tunings; - } - - public void addTuning(Tuning tuning) { - this.tunings.add(tuning); - } - - public void removeTuning(Tuning tuning) { - this.tunings.remove(tuning); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Accessory accessory)) { - return false; - } - return Objects.equals(slotName, accessory.slotName) - && Objects.equals(accessoryName, accessory.accessoryName) - && Objects.equals(tunings, accessory.tunings); - } - - @Override - public int hashCode() { - return Objects.hash(slotName, accessoryName, tunings); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java deleted file mode 100644 index c71cee7..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import com.onixbyte.deltaforceguide.domain.converter.FirearmTypeConverter; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing a firearm in the Delta Force game. - * - * @author zihluwang - */ -@Entity -@Table(name = "firearm") -public class Firearm { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "name", nullable = false, length = 64) - private String name; - - @Column(name = "type", nullable = false) - @Convert(converter = FirearmTypeConverter.class) - private FirearmType type; - - @Column(name = "level", nullable = false) - private String level; - - @Column(name = "review", columnDefinition = "TEXT") - private String review; - - @Column(name = "calibre") - private String calibre; - - @Column(name = "fire_rate") - private Integer fireRate; - - @Column(name = "armour_damage") - private Integer armourDamage; - - @Column(name = "body_damage") - private Integer bodyDamage; - - @OneToMany(mappedBy = "firearm", cascade = CascadeType.ALL, orphanRemoval = true) - private List modifications = new ArrayList<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public FirearmType getType() { - return type; - } - - public void setType(FirearmType type) { - this.type = type; - } - - public String getLevel() { - return level; - } - - public void setLevel(String level) { - this.level = level; - } - - public String getReview() { - return review; - } - - public void setReview(String review) { - this.review = review; - } - - public String getCalibre() { - return calibre; - } - - public void setCalibre(String calibre) { - this.calibre = calibre; - } - - public Integer getFireRate() { - return fireRate; - } - - public void setFireRate(Integer fireRate) { - this.fireRate = fireRate; - } - - public Integer getArmourDamage() { - return armourDamage; - } - - public void setArmourDamage(Integer armourDamage) { - this.armourDamage = armourDamage; - } - - public Integer getBodyDamage() { - return bodyDamage; - } - - public void setBodyDamage(Integer bodyDamage) { - this.bodyDamage = bodyDamage; - } - - public List getModifications() { - return modifications; - } - - public void setModifications(List modifications) { - this.modifications = modifications; - } - - public void addModification(Modification modification) { - this.modifications.add(modification); - modification.setFirearm(this); - } - - public void removeModification(Modification modification) { - this.modifications.remove(modification); - modification.setFirearm(null); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private String name; - private FirearmType type; - private String level; - private String review; - private String calibre; - private Integer fireRate; - private Integer armourDamage; - private Integer bodyDamage; - private List modifications; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder type(FirearmType type) { - this.type = type; - return this; - } - - public Builder level(String level) { - this.level = level; - return this; - } - - public Builder review(String review) { - this.review = review; - return this; - } - - public Builder calibre(String calibre) { - this.calibre = calibre; - return this; - } - - public Builder fireRate(Integer fireRate) { - this.fireRate = fireRate; - return this; - } - - public Builder armourDamage(Integer armourDamage) { - this.armourDamage = armourDamage; - return this; - } - - public Builder bodyDamage(Integer bodyDamage) { - this.bodyDamage = bodyDamage; - return this; - } - - public Builder modifications(List modifications) { - this.modifications = modifications; - return this; - } - - public Firearm build() { - Firearm firearm = new Firearm(); - firearm.id = this.id; - firearm.name = this.name; - firearm.type = this.type; - firearm.level = this.level; - firearm.review = this.review; - firearm.calibre = this.calibre; - firearm.fireRate = this.fireRate; - firearm.armourDamage = this.armourDamage; - firearm.bodyDamage = this.bodyDamage; - firearm.modifications = this.modifications == null ? new ArrayList<>() : this.modifications; - return firearm; - } - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java deleted file mode 100644 index a1942f7..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import jakarta.persistence.*; -import org.hibernate.annotations.Type; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing a firearm modification or build configuration. - * - * @author zihluwang - */ -@Entity -@Table( - name = "modification", - indexes = { - @Index(name = "idx_modification_firearm_id", columnList = "firearm_id") - } -) -public class Modification { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "firearm_id", nullable = false, foreignKey = @ForeignKey(name = "fk_modification_firearm")) - private Firearm firearm; - - @Column(name = "name", nullable = false, length = 64) - private String name; - - @Column(name = "code", nullable = false, length = 64) - private String code; - - @Type(JsonType.class) - @Column(name = "tags", columnDefinition = "jsonb") - private List tags = new ArrayList<>(); - - @Type(JsonType.class) - @Column(name = "accessories", columnDefinition = "jsonb") - private List accessories = new ArrayList<>(); - - @Column(name = "note", columnDefinition = "TEXT") - private String note; - - @Column(name = "author", length = 64) - private String author; - - @Column(name = "video_url", length = 512) - private String videoUrl; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Firearm getFirearm() { - return firearm; - } - - public void setFirearm(Firearm firearm) { - this.firearm = firearm; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public List getAccessories() { - return accessories; - } - - public void setAccessories(List accessories) { - this.accessories = accessories; - } - - public void addAccessory(Accessory modificationAccessory) { - this.accessories.add(modificationAccessory); - } - - public void removeAccessory(Accessory modificationAccessory) { - this.accessories.remove(modificationAccessory); - } - - public String getNote() { - return note; - } - - public void setNote(String note) { - this.note = note; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getVideoUrl() { - return videoUrl; - } - - public void setVideoUrl(String videoUrl) { - this.videoUrl = videoUrl; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private Firearm firearm; - private String name; - private String code; - private List tags; - private List accessories; - private String note; - private String author; - private String videoUrl; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder firearm(Firearm firearm) { - this.firearm = firearm; - return this; - } - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder code(String code) { - this.code = code; - return this; - } - - public Builder tags(List tags) { - this.tags = tags; - return this; - } - - public Builder accessories(List accessories) { - this.accessories = accessories; - return this; - } - - public Builder note(String note) { - this.note = note; - return this; - } - - public Builder author(String author) { - this.author = author; - return this; - } - - public Builder videoUrl(String videoUrl) { - this.videoUrl = videoUrl; - return this; - } - - public Modification build() { - Modification modification = new Modification(); - modification.id = this.id; - modification.firearm = this.firearm; - modification.name = this.name; - modification.code = this.code; - modification.tags = this.tags == null ? new ArrayList<>() : this.tags; - modification.accessories = this.accessories == null ? new ArrayList<>() : this.accessories; - modification.note = this.note; - modification.author = this.author; - modification.videoUrl = this.videoUrl; - return modification; - } - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java deleted file mode 100644 index a4eb1f1..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import java.util.Objects; - -/** - * Entity representing a tuning adjustment for an accessory, stored as JSONB within Accessory. - * - * @author zihluwang - */ -public class Tuning { - - private String tuningName; - private Double tuningValue; - - public Tuning() { - } - - public String getTuningName() { - return tuningName; - } - - public void setTuningName(String tuningName) { - this.tuningName = tuningName; - } - - public Double getTuningValue() { - return tuningValue; - } - - public void setTuningValue(Double tuningValue) { - this.tuningValue = tuningValue; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Tuning tuning)) { - return false; - } - return Objects.equals(tuningName, tuning.tuningName) - && Objects.equals(tuningValue, tuning.tuningValue); - } - - @Override - public int hashCode() { - return Objects.hash(tuningName, tuningValue); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java deleted file mode 100644 index d696b2d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -import java.util.ArrayList; -import java.util.List; - -/** - * Entity representing an application user with authentication credentials. - * - * @author zihluwang - */ -@Entity -@Table(name = "app_user") -public class User { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "username", nullable = false) - private String username; - - @Column(name = "email", nullable = false) - private String email; - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List credentials = new ArrayList<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public List getCredentials() { - return credentials; - } - - public void setCredentials(List credentials) { - this.credentials = credentials; - } - - public void addCredential(UserCredential credential) { - this.credentials.add(credential); - credential.setUser(this); - } - - public void removeCredential(UserCredential credential) { - this.credentials.remove(credential); - credential.setUser(null); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long id; - private String username; - private String email; - private List credentials; - - public Builder id(Long id) { - this.id = id; - return this; - } - - public Builder username(String username) { - this.username = username; - return this; - } - - public Builder email(String email) { - this.email = email; - return this; - } - - public Builder credentials(List credentials) { - this.credentials = credentials; - return this; - } - - public User build() { - User user = new User(); - user.id = this.id; - user.username = this.username; - user.email = this.email; - user.credentials = this.credentials == null ? new ArrayList<>() : this.credentials; - return user; - } - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java deleted file mode 100644 index 2ecdd37..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.AttributeOverride; -import jakarta.persistence.AttributeOverrides; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.MapsId; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -/** - * Entity representing a user credential linked to an authentication provider. - * - * @author zihluwang - */ -@Entity -@Table(name = "app_user_credential") -public class UserCredential { - - @EmbeddedId - @AttributeOverrides({ - @AttributeOverride(name = "userId", column = @Column(name = "user_id")), - @AttributeOverride(name = "provider", column = @Column(name = "provider")) - }) - private UserCredentialId id = new UserCredentialId(); - - @MapsId("userId") - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_credential_user")) - private User user; - - @Column(name = "credential", nullable = false) - private String credential; - - public UserCredentialId getId() { - return id; - } - - public void setId(UserCredentialId id) { - this.id = id; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setUserId(user == null ? null : user.getId()); - } - - public Long getUserId() { - return id == null ? null : id.getUserId(); - } - - public void setUserId(Long userId) { - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setUserId(userId); - } - - public String getProvider() { - return id == null ? null : id.getProvider(); - } - - public void setProvider(String provider) { - if (this.id == null) { - this.id = new UserCredentialId(); - } - this.id.setProvider(provider); - } - - public String getCredential() { - return credential; - } - - public void setCredential(String credential) { - this.credential = credential; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private UserCredentialId id; - private User user; - private Long userId; - private String provider; - private String credential; - - public Builder id(UserCredentialId id) { - this.id = id; - return this; - } - - public Builder user(User user) { - this.user = user; - return this; - } - - public Builder userId(Long userId) { - this.userId = userId; - return this; - } - - public Builder provider(String provider) { - this.provider = provider; - return this; - } - - public Builder credential(String credential) { - this.credential = credential; - return this; - } - - public UserCredential build() { - UserCredential userCredential = new UserCredential(); - userCredential.id = this.id == null ? new UserCredentialId() : this.id; - userCredential.user = this.user; - if (this.user != null) { - userCredential.id.setUserId(this.user.getId()); - } - if (this.userId != null) { - userCredential.id.setUserId(this.userId); - } - if (this.provider != null) { - userCredential.id.setProvider(this.provider); - } - userCredential.credential = this.credential; - return userCredential; - } - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java deleted file mode 100644 index 1e25f45..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.domain.entity; - -import jakarta.persistence.Embeddable; - -import java.io.Serializable; -import java.util.Objects; - -/** - * Composite key for the UserCredential entity, combining user ID and provider. - * - * @author zihluwang - */ -@Embeddable -public class UserCredentialId implements Serializable { - - private Long userId; - - private String provider; - - public Long getUserId() { - return userId; - } - - public void setUserId(Long userId) { - this.userId = userId; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UserCredentialId that = (UserCredentialId) o; - return Objects.equals(userId, that.userId) && Objects.equals(provider, that.provider); - } - - @Override - public int hashCode() { - return Objects.hash(userId, provider); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Long userId; - private String provider; - - public Builder userId(Long userId) { - this.userId = userId; - return this; - } - - public Builder provider(String provider) { - this.provider = provider; - return this; - } - - public UserCredentialId build() { - UserCredentialId id = new UserCredentialId(); - id.userId = this.userId; - id.provider = this.provider; - return id; - } - } -} - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java b/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java deleted file mode 100644 index 77bdbdb..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.onixbyte.deltaforceguide.enumeration; - -/** - * Enumeration of firearm types in the Delta Force game. - * Each type is associated with an integer code used for database persistence. - * - * @author zihluwang - */ -public enum FirearmType { - - RIFLE(0), - SUB_MACHINE_GUN(1), - SHOTGUN(2), - LIGHT_MACHINE_GUN(3), - DESIGNATED_MARKSMAN_RIFLE(4), - SNIPER_RIFLE(5), - PISTOL(6), - SPECIAL(7); - - private final int code; - - FirearmType(int code) { - this.code = code; - } - - public int getCode() { - return code; - } - - /** - * Resolve a FirearmType from its integer code. - * - * @param code the integer code, may be null - * @return the corresponding FirearmType, or null if the code is null - * @throws IllegalArgumentException if the code does not match any known type - */ - public static FirearmType fromCode(Integer code) { - if (code == null) { - return null; - } - - for (FirearmType type : values()) { - if (type.code == code) { - return type; - } - } - - throw new IllegalArgumentException("Unknown FirearmType code: " + code); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java b/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java deleted file mode 100644 index d1b7ff2..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.onixbyte.deltaforceguide.exeption; - -import org.springframework.http.HttpStatus; - -/** - * Custom runtime exception that carries an HTTP status code for API error responses. - * - * @author zihluwang - */ -public class BizException extends RuntimeException { - - /** - * The HTTP status code associated with this business exception. - *

- * This status code indicates the appropriate HTTP response status that should be returned to - * clients when this exception occurs. It enables consistent error handling across - * REST API endpoints. - */ - private final HttpStatus status; - - public BizException(String message) { - super(message); - this.status = HttpStatus.INTERNAL_SERVER_ERROR; - } - - public BizException(HttpStatus status, String message) { - super(message); - this.status = status; - } - - /** - * Returns the HTTP status code associated with this business exception. - * - * @return the HTTP status code that should be used in the error response - */ - public HttpStatus getStatus() { - return status; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java b/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java deleted file mode 100644 index e2068a6..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.onixbyte.deltaforceguide.filter; - -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.onixbyte.deltaforceguide.client.TokenClient; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.UserManager; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import com.onixbyte.deltaforceguide.service.CookieService; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.util.WebUtils; - -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; -import java.util.Objects; -import java.util.Optional; - -/** - * Servlet filter that extracts and validates JWT tokens from httpOnly cookies for each request. - * - * @author zihluwang - */ -@Component -public class TokenAuthenticationFilter extends OncePerRequestFilter { - - private final static Logger log = LoggerFactory.getLogger(TokenAuthenticationFilter.class); - private static final Duration ACCESS_TOKEN_RENEW_THRESHOLD = Duration.ofMinutes(5); - - private final UserManager userManager; - private final TokenClient tokenClient; - private final CookieService cookieService; - private final HandlerExceptionResolver handlerExceptionResolver; - - public TokenAuthenticationFilter( - UserManager userManager, - TokenClient tokenClient, - CookieService cookieService, - HandlerExceptionResolver handlerExceptionResolver - ) { - this.userManager = userManager; - this.tokenClient = tokenClient; - this.cookieService = cookieService; - this.handlerExceptionResolver = handlerExceptionResolver; - } - - /** - * Extracts JWT from httpOnly cookie, validates it, and sets the security context. - * - * @param request the HTTP request - * @param response the HTTP response - * @param filterChain the filter chain - */ - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain - ) throws ServletException, IOException { - var token = Optional.ofNullable(WebUtils.getCookie(request, "AccessToken")) - .map(Cookie::getValue) - .orElse(null); - if (Objects.isNull(token) || token.isBlank()) { - filterChain.doFilter(request, response); - return; - } - - try { - var decodedToken = tokenClient.verifyToken(token); - var username = decodedToken.getSubject(); - - var userWrapper = userManager.findByUsername(username); - if (userWrapper.isEmpty()) { - throw new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录"); - } - - var user = userWrapper.get(); - var authentication = UsernamePasswordAuthentication.authenticated(user); - SecurityContextHolder.getContext().setAuthentication(authentication); - - if (shouldRenew(decodedToken.getExpiresAt().toInstant())) { - var renewedToken = tokenClient.generateToken(user); - var renewedTokenCookie = cookieService.buildCookie("AccessToken", renewedToken); - response.addHeader(HttpHeaders.SET_COOKIE, renewedTokenCookie.toString()); - } - - filterChain.doFilter(request, response); - } catch (JWTVerificationException e) { - log.error("JWT verification failed.", e); - handlerExceptionResolver.resolveException(request, response, null, - new BizException(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录")); - } catch (BizException e) { - handlerExceptionResolver.resolveException(request, response, null, e); - } - } - - private boolean shouldRenew(Instant expiresAt) { - return Duration.between(Instant.now(), expiresAt).compareTo(ACCESS_TOKEN_RENEW_THRESHOLD) < 0; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java b/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java deleted file mode 100644 index ddfd071..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.onixbyte.deltaforceguide.filter; - -import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper; -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Component -public class WebhookFilter implements Filter { - @Override - public void doFilter( - ServletRequest request, - ServletResponse response, - FilterChain chain - ) throws IOException, ServletException { - var httpRequest = (HttpServletRequest) request; - var wrappedRequest = new RepeatedlyReadRequestWrapper(httpRequest); - chain.doFilter(wrappedRequest, response); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java b/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java deleted file mode 100644 index c1b552a..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.onixbyte.deltaforceguide.interceptor; - -import com.onixbyte.crypto.util.CryptoUtil; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.WebhookManager; -import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader; -import com.onixbyte.deltaforceguide.wrapper.RepeatedlyReadRequestWrapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Verifies GitHub webhook requests by validating the {@code X-Hub-Signature-256} - * header against the configured secret using HMAC-SHA256. - * - *

Verification is skipped when no secret is configured. The signature format is - * {@code sha256=} as documented by GitHub. - */ -@Component -public class GitHubWebhookInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory.getLogger(GitHubWebhookInterceptor.class); - - private final WebhookManager webhookManager; - - public GitHubWebhookInterceptor(WebhookManager webhookManager) { - this.webhookManager = webhookManager; - } - - @Override - public boolean preHandle( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull Object handler - ) { - if (!(request instanceof RepeatedlyReadRequestWrapper req)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, - "Request body is not readable"); - } - - var secret = webhookManager.secret(); - if (secret == null || secret.isBlank()) { - log.debug("No GitHub webhook secret configured, skipping signature verification"); - return true; - } - - var signatureHeader = req.getHeader(GitHubWebhookHeader.SIGNATURE_256); - if (signatureHeader == null || signatureHeader.isBlank()) { - log.warn("Missing {} header from ip={}", - GitHubWebhookHeader.SIGNATURE_256, request.getRemoteAddr()); - throw new BizException(HttpStatus.UNAUTHORIZED, - "Missing webhook signature header"); - } - - var body = req.getBodyString(); - try { - var computed = "sha256=" + CryptoUtil.hmacSha256(body, secret); - - if (!MessageDigest.isEqual( - computed.getBytes(StandardCharsets.UTF_8), - signatureHeader.getBytes(StandardCharsets.UTF_8))) { - log.warn("Invalid webhook signature from ip={}", request.getRemoteAddr()); - throw new BizException(HttpStatus.UNAUTHORIZED, "Invalid webhook signature"); - } - - return true; - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - log.error("Failed to compute HMAC-SHA256", e); - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, - "Failed to verify webhook signature"); - } - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java b/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java deleted file mode 100644 index c7aac50..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onixbyte.deltaforceguide.interceptor; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -@Component -public class TrafficInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory.getLogger(TrafficInterceptor.class); - - @Override - public boolean preHandle( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull Object handler - ) { - var ip = resolveClientIp(request); - var method = request.getMethod(); - var uri = request.getRequestURI(); - var query = request.getQueryString(); - var contentType = request.getContentType(); - var contentLength = request.getContentLength(); - var userAgent = request.getHeader("User-Agent"); - - log.debug("Request method={}, uri={}, query={}, ip={}, content-type={}, content-length={}, user-agent={}", - method, uri, query, ip, contentType, contentLength, userAgent); - return true; - } - - private String resolveClientIp(HttpServletRequest request) { - var xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isBlank()) { - return xForwardedFor.split(",")[0].trim(); - } - var xRealIp = request.getHeader("X-Real-IP"); - if (xRealIp != null && !xRealIp.isBlank()) { - return xRealIp.trim(); - } - return request.getRemoteAddr(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java deleted file mode 100644 index 378fe9b..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.AppProperties; -import org.springframework.stereotype.Component; - -@Component -public class AppManager { - - private final AppProperties appProperties; - - public AppManager(AppProperties appProperties) { - this.appProperties = appProperties; - } - - /** - * Retrieves the application version. - * - * @return the version string of this application - */ - public String getVersion() { - return "v%s-%s by @%s".formatted( - appProperties.version(), - appProperties.channel(), - appProperties.vendor() - ); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java deleted file mode 100644 index f6ef16f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.CookieProperties; -import org.springframework.boot.web.server.Cookie; -import org.springframework.stereotype.Component; - -import java.time.Duration; - -/** - * Manager providing cookie construction operations with configurable properties. - * - * @author zihluwang - */ -@Component -public class CookieManager { - - private final CookieProperties cookieProperties; - - public CookieManager(CookieProperties cookieProperties) { - this.cookieProperties = cookieProperties; - } - - public Boolean getHttpOnly() { - return cookieProperties.httpOnly(); - } - - public Boolean getSecure() { - return cookieProperties.secure(); - } - - public Cookie.SameSite getSameSite() { - return cookieProperties.sameSite(); - } - - public String getPath() { - return cookieProperties.path(); - } - - public Duration getMaxAge() { - return cookieProperties.maxAge(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java deleted file mode 100644 index 6b6ac59..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.shared.JacksonModules; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; - -import java.time.Duration; -import java.time.LocalDate; -import java.util.Objects; - -/** - * Manager for daily password data access and caching coordination. - * - * @author zihluwang - */ -@Component -public class DailyPasswordManager { - - private static final String CACHE_KEY_PREFIX = "daily-password:"; - - private final RestClient restClient; - private final RedisTemplate redisTemplate; - - @Autowired - public DailyPasswordManager( - RestClient.Builder restClientBuilder, - RedisTemplate redisTemplate - ) { - var snakeCaseMapper = new ObjectMapper(); - snakeCaseMapper.setPropertyNamingStrategy( - PropertyNamingStrategies.SnakeCaseStrategy.INSTANCE); - snakeCaseMapper.configure( - DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - snakeCaseMapper.registerModule(JacksonModules.DATE_TIME_MODULE); - - this.restClient = restClientBuilder - .baseUrl("https://tmini.net/api") - .messageConverters(converters -> { - converters.removeIf( - MappingJackson2HttpMessageConverter.class::isInstance); - converters.add( - new MappingJackson2HttpMessageConverter(snakeCaseMapper)); - }) - .build(); - this.redisTemplate = redisTemplate; - } - - /** - * Retrieves the daily password from cache or generates a new one. - * @return the daily password response - */ - public DailyPasswordResponse getDailyPassword() { - var key = CACHE_KEY_PREFIX + LocalDate.now(); - - var cached = redisTemplate.opsForValue().get(key); - if (cached != null) { - return (DailyPasswordResponse) cached; - } - - var response = restClient.get() - .uri((uriBuilder) -> uriBuilder - .path("/sjzmm") - .queryParam("ckey", "") - .queryParam("type", "json") - .build()) - .retrieve() - .body(DailyPasswordResponse.class); - - if (Objects.isNull(response)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "暂无每日密码数据。"); - } - - redisTemplate.opsForValue().set(key, response, Duration.ofDays(1L)); - return response; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java deleted file mode 100644 index f7defab..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.Accessory; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import com.onixbyte.deltaforceguide.domain.entity.Tuning; -import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.TuningRequest; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import com.onixbyte.deltaforceguide.repository.ModificationRepository; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -@Component -public class ModificationManager { - - private final ModificationRepository modificationRepository; - private final FirearmRepository firearmRepository; - - public ModificationManager( - ModificationRepository modificationRepository, - FirearmRepository firearmRepository - ) { - this.modificationRepository = modificationRepository; - this.firearmRepository = firearmRepository; - } - - @Transactional - public ModificationResponse create(ModificationRequest request) { - var firearm = firearmRepository.findById(request.firearmId()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found: " + request.firearmId())); - var modification = toEntity(request, firearm); - return ModificationResponse.from(modificationRepository.save(modification)); - } - - @Transactional - public List batchCreate(List requests) { - var firearmIds = requests.stream() - .map(ModificationRequest::firearmId) - .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)); - - Map firearmMap = new HashMap<>(); - firearmRepository.findAllById(firearmIds) - .forEach(firearm -> firearmMap.put(firearm.getId(), firearm)); - - if (firearmMap.size() != firearmIds.size()) { - var missing = firearmIds.stream() - .filter((id) -> !firearmMap.containsKey(id)) - .toList(); - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found: " + missing); - } - - var modifications = requests.stream() - .map(req -> toEntity(req, firearmMap.get(req.firearmId()))) - .toList(); - return modificationRepository.saveAll(modifications) - .stream() - .map(ModificationResponse::from) - .toList(); - } - - public Long resolveFirearmId(Long firearmId, String firearmName) { - if (firearmId != null) { - return firearmId; - } - if (firearmName == null || firearmName.isBlank()) { - return null; - } - var matches = firearmRepository.findByName(firearmName); - if (matches.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - "Firearm not found by name: " + firearmName); - } - return matches.getFirst().getId(); - } - - private Modification toEntity(ModificationRequest request, Firearm firearm) { - return Modification.builder() - .firearm(firearm) - .name(request.name()) - .code(request.code()) - .tags(request.tags()) - .accessories(toAccessories(request.accessories())) - .note(request.note()) - .author(request.author()) - .videoUrl(request.videoUrl()) - .build(); - } - - private List toAccessories(List requests) { - if (requests == null) { - return new ArrayList<>(); - } - return requests.stream().map(this::toAccessory).toList(); - } - - private Accessory toAccessory(AccessoryRequest request) { - var accessory = new Accessory(); - accessory.setSlotName(request.slotName()); - accessory.setAccessoryName(request.accessoryName()); - accessory.setTunings(toTunings(request.tunings())); - return accessory; - } - - private List toTunings(List requests) { - if (requests == null) { - return new ArrayList<>(); - } - return requests.stream().map(this::toTuning).toList(); - } - - private Tuning toTuning(TuningRequest request) { - var tuning = new Tuning(); - tuning.setTuningName(request.tuningName()); - tuning.setTuningValue(request.tuningValue()); - return tuning; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java deleted file mode 100644 index d77c687..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.repository.UserCredentialRepository; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Optional; - -/** - * Manager for user credential persistence and authentication data access. - * - * @author zihluwang - */ -@Component -public class UserCredentialManager { - - private final UserCredentialRepository userCredentialRepository; - - public UserCredentialManager(UserCredentialRepository userCredentialRepository) { - this.userCredentialRepository = userCredentialRepository; - } - - /** - * Finds all credentials belonging to a specific user. - * - * @param userId the user ID - * @return list of matching credentials - */ - public List findAllByUserId(Long userId) { - return userCredentialRepository.findAllByUserId(userId); - } - - /** - * Finds a credential for a specific user and provider combination. - * - * @param userId the user ID - * @param provider the authentication provider - * @return the matching credential, if found - */ - public Optional findByUserIdAndProvider(Long userId, String provider) { - return userCredentialRepository.findByUserIdAndProvider(userId, provider); - } - - /** - * Persists a new or updated credential. - * - * @param userCredential the credential to save - * @return the saved credential - */ - public UserCredential save(UserCredential userCredential) { - return userCredentialRepository.save(userCredential); - } - - /** - * Deletes a credential for a specific user and provider. - * - * @param userId the user ID - * @param provider the authentication provider - */ - public void deleteByUserIdAndProvider(Long userId, String provider) { - userCredentialRepository.deleteByUserIdAndProvider(userId, provider); - } - - /** - * Deletes all credentials belonging to a user. - * - * @param userId the user ID - */ - public void deleteAllByUserId(Long userId) { - userCredentialRepository.deleteAllByUserId(userId); - } -} - - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java deleted file mode 100644 index d63c0f7..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.repository.UserRepository; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; - -/** - * Manager for user entity persistence and query operations. - * - * @author zihluwang - */ -@Component -public class UserManager { - - private final UserRepository userRepository; - - public UserManager(UserRepository userRepository) { - this.userRepository = userRepository; - } - - /** - * Finds a user by their ID. - * - * @param id the user ID - * @return the matching user, if found - */ - public Optional findById(Long id) { - return userRepository.findById(id); - } - - /** - * Retrieves all registered users. - * @return list of all users - */ - public List findAll() { - return userRepository.findAll(); - } - - /** - * Finds a user by their username. - * - * @param username the username to search for - * @return the matching user, if found - */ - public Optional findByUsername(String username) { - return userRepository.findByUsername(username); - } - - @Transactional(readOnly = true) - public Optional findByEmail(String email) { - return userRepository.findByEmail(email); - } - - /** - * Persists a new or updated user. - * - * @param user the user to save - * @return the saved user - */ - public User save(User user) { - return userRepository.save(user); - } - - /** - * Deletes a user by their ID. - * - * @param id the user ID to delete - */ - public void deleteById(Long id) { - userRepository.deleteById(id); - } - - /** - * Finds a user by their username or email address. - * - * @param principal the username or email to search for - * @return the matching user, if found - */ - public Optional findByUsernameOrEmail(String principal) { - return userRepository.findByUsernameOrEmail(principal); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java b/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java deleted file mode 100644 index 21c5b52..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.manager; - -import com.onixbyte.deltaforceguide.properties.GitHubWebhookProperties; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class WebhookManager { - - private final GitHubWebhookProperties gitHubWebhookProperties; - - public WebhookManager(GitHubWebhookProperties gitHubWebhookProperties) { - this.gitHubWebhookProperties = gitHubWebhookProperties; - } - - public String secret() { - return gitHubWebhookProperties.secret(); - } - - public List allowedUsers() { - return gitHubWebhookProperties.allowedUsers(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java b/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java deleted file mode 100644 index b13bca2..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "app.common") -public record AppProperties( - String version, - String channel, - String vendor -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java b/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java deleted file mode 100644 index 0cfde08..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.DefaultValue; -import org.springframework.boot.web.server.Cookie; - -import java.time.Duration; - -/** - * Configuration properties for HTTP cookies used in authentication, prefixed with "app.cookie". - * - * @param httpOnly whether the cookie is httpOnly - * @param secure whether the cookie is secure - * @param path the cookie path - * @param maxAge the maximum age of the cookie - * @param sameSite the SameSite policy for the cookie - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.cookie") -public record CookieProperties( - @DefaultValue("true") Boolean httpOnly, - @DefaultValue("true") Boolean secure, - @DefaultValue("/") String path, - @DefaultValue("PT2H") Duration maxAge, - @DefaultValue("LAX") Cookie.SameSite sameSite -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java b/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java deleted file mode 100644 index 193640f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.DefaultValue; -import org.springframework.http.HttpMethod; - -import java.time.Duration; - -/** - * Configuration properties for CORS settings, prefixed with "app.cors". - * - * @param allowedHeaders headers allowed in CORS requests - * @param allowedMethods HTTP methods allowed in CORS requests - * @param allowedOrigins origins permitted to make cross-origin requests - * @param allowCredentials whether credentials are allowed in CORS requests - * @param allowPrivateNetwork whether private network access is permitted - * @param maxAge how long the CORS preflight response may be cached - * @param exposedHeaders headers exposed to the client in CORS responses - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.cors") -public record CorsProperties( - @DefaultValue({"Content-Type", "Authorization"}) - String[] allowedHeaders, - @DefaultValue({"GET", "POST", "PUT", "PATCH", "DELETE"}) - HttpMethod[] allowedMethods, - String[] allowedOrigins, - boolean allowCredentials, - boolean allowPrivateNetwork, - @DefaultValue("PT2H") - Duration maxAge, - @DefaultValue({"Content-Type", "Authorization"}) - String[] exposedHeaders -) { -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java b/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java deleted file mode 100644 index 673051a..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.List; - -@ConfigurationProperties(prefix = "app.webhook.github") -public record GitHubWebhookProperties( - String secret, - List allowedUsers -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java b/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java deleted file mode 100644 index 74c693c..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.onixbyte.deltaforceguide.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.time.Duration; - -/** - * Configuration properties for JWT token generation and validation, prefixed with "app.jwt". - * - * @param issuer the JWT issuer claim - * @param secret the signing secret for JWT tokens - * @param validTime the duration for which a token remains valid - * @author zihluwang - */ -@ConfigurationProperties(prefix = "app.jwt") -public record TokenProperties( - String issuer, - String secret, - Duration validTime -) { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java deleted file mode 100644 index 46c84b9..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * Spring Data JPA repository for {@link Firearm} entity operations. - * - * @author zihluwang - */ -@Repository -public interface FirearmRepository extends JpaRepository { - - Page findAllByType(FirearmType type, Pageable pageable); - - List findByName(String name); -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java deleted file mode 100644 index 3d9fcc0..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import org.jspecify.annotations.NonNull; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link Modification} entity operations, - * including native JSONB tag filtering for Postgres. - * - * @author zihluwang - */ -@Repository -public interface ModificationRepository extends JpaRepository { - - @EntityGraph(attributePaths = {"firearm"}) - Page findAllBy(Pageable pageable); - - @EntityGraph(attributePaths = {"firearm"}) - Page findAllByFirearm_Id(Long firearmId, Pageable pageable); - - @Override - @EntityGraph(attributePaths = {"firearm"}) - @NonNull - Optional findById(@NonNull Long id); - - /** - * Page query modifications with optional firearm and JSONB tag filtering. - * - * @param firearmId optional firearm ID filter (nullable) - * @param tagsJson optional JSON array of tags to match via Postgres {@code @>} operator (nullable) - * @param pageable pagination information - * @return a page of matching modifications - */ - @Query(value = """ - SELECT * FROM modification m - WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) - AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) - """, - countQuery = """ - SELECT count(*) FROM modification m - WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) - AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) - """, - nativeQuery = true) - Page pageQueryByFirearmAndTags(@Param("firearmId") Long firearmId, @Param("tagsJson") String tagsJson, Pageable pageable); - - /** - * Retrieve all distinct tag values from modifications, optionally filtered by firearm. - * - * @param firearmId optional firearm ID filter (nullable) - * @return list of distinct tag strings - */ - @Query(value = "SELECT DISTINCT jsonb_array_elements_text(cast(tags as jsonb)) FROM modification WHERE (:firearmId IS NULL OR firearm_id = :firearmId)", nativeQuery = true) - List findAllTags(@Param("firearmId") Long firearmId); -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java deleted file mode 100644 index 53776e3..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link UserCredential} entity operations. - * - * @author zihluwang - */ -@Repository -public interface UserCredentialRepository extends JpaRepository { - - /** - * Find all credentials belonging to a given user. - * - * @param userId the user ID - * @return list of matching credentials - */ - @EntityGraph(attributePaths = {"user"}) - @Query(""" - select uc - from UserCredential uc - where uc.user.id = :userId - """) - List findAllByUserId(@Param("userId") Long userId); - - /** - * Find a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider identifier - * @return an optional containing the matching credential, or empty if not found - */ - @EntityGraph(attributePaths = {"user"}) - @Query(""" - select uc - from UserCredential uc - where uc.user.id = :userId - and uc.id.provider = :provider - """) - Optional findByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider); - - /** - * Delete a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider identifier - */ - @Modifying - @Query(""" - delete from UserCredential uc - where uc.user.id = :userId - and uc.id.provider = :provider - """) - void deleteByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider); - - /** - * Delete all credentials for a given user. - * - * @param userId the user ID - */ - @Modifying - @Query(""" - delete from UserCredential uc - where uc.user.id = :userId - """) - void deleteAllByUserId(@Param("userId") Long userId); -} - - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java deleted file mode 100644 index 7a0a363..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.onixbyte.deltaforceguide.repository; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import org.jspecify.annotations.NonNull; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -/** - * Spring Data JPA repository for {@link User} entity operations. - * - * @author zihluwang - */ -@Repository -public interface UserRepository extends JpaRepository { - - @Override - @EntityGraph(attributePaths = {"credentials"}) - @NonNull - Optional findById(@NonNull Long id); - - @EntityGraph(attributePaths = {"credentials"}) - Optional findByUsername(String username); - - @EntityGraph(attributePaths = {"credentials"}) - Optional findByEmail(String email); - - boolean existsByUsername(String username); - - boolean existsByEmail(String email); - - /** - * Find a user by either username or email. - * - * @param principal the username or email to search for - * @return an optional containing the matching user, or empty if not found - */ - @EntityGraph(attributePaths = {"credentials"}) - @Query(""" - select u - from User u - where u.username = :principal - or u.email = :principal - """) - Optional findByUsernameOrEmail(@Param("principal") String principal); -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java b/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java deleted file mode 100644 index 9b6b014..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.onixbyte.deltaforceguide.security.annotation; - -import org.springframework.security.access.prepost.PreAuthorize; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark controller endpoints that require authentication. - * - * @author zihluwang - */ -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("isAuthenticated()") -public @interface RequiresAuth { -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java b/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java deleted file mode 100644 index 9bd2ae3..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.onixbyte.deltaforceguide.security.authentication; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.CredentialsContainer; -import org.springframework.security.core.GrantedAuthority; - -import java.util.Collection; -import java.util.List; - -/** - * Custom authentication token for username/password-based login flows. - * - * @author zihluwang - */ -public class UsernamePasswordAuthentication implements Authentication, CredentialsContainer { - private final String username; - private String password; - private boolean authenticated; - private User user; - - private UsernamePasswordAuthentication(String username, String password, boolean authenticated, User user) { - this.username = username; - this.password = password; - this.authenticated = authenticated; - this.user = user; - } - - public static UsernamePasswordAuthentication unauthenticated(String username, String password) { - return new UsernamePasswordAuthentication(username, password, false, null); - } - - public static UsernamePasswordAuthentication authenticated(User user) { - return new UsernamePasswordAuthentication(user.getUsername(), null, true, user); - } - - @Override - public Collection getAuthorities() { - return List.of(); - } - - @Override - public String getCredentials() { - return password; - } - - @Override - public User getDetails() { - return user; - } - - @Override - public String getPrincipal() { - return username; - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void setAuthenticated(boolean authenticated) throws IllegalArgumentException { - this.authenticated = authenticated; - } - - @Override - public String getName() { - return username; - } - - @Override - public void eraseCredentials() { - this.password = null; - } - - public void setDetails(User user) { - this.user = user; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java b/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java deleted file mode 100644 index 01e6baa..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.onixbyte.deltaforceguide.security.provider; - -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.manager.UserManager; -import com.onixbyte.deltaforceguide.repository.UserCredentialRepository; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import com.onixbyte.deltaforceguide.shared.CredentialProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Example; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -/** - * Authentication provider that validates username/password credentials against stored BCrypt hashes. - * - * @author zihluwang - */ -@Component -public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider { - - private static final Logger log = LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class); - private final UserManager userManager; - private final PasswordEncoder passwordEncoder; - private final UserCredentialRepository userCredentialRepository; - - @Autowired - public UsernamePasswordAuthenticationProvider( - UserManager userManager, - PasswordEncoder passwordEncoder, - UserCredentialRepository userCredentialRepository - ) { - this.userManager = userManager; - this.passwordEncoder = passwordEncoder; - this.userCredentialRepository = userCredentialRepository; - } - - /** - * Validates the username/password credentials against stored BCrypt hashes. - * - * @param authentication the authentication request object - * @return a fully authenticated object including user details - */ - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) { - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "用户认证失败,请稍后再试。"); - } - - // get userContainer from database - var userContainer = userManager.findByUsernameOrEmail(usernamePasswordAuthentication.getPrincipal()); - if (userContainer.isEmpty()) { - log.error("User {} is trying to authenticate but no userContainer found.", usernamePasswordAuthentication.getPrincipal()); - throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); - } - - var user = userContainer.get(); - - var userCredentialExample = new UserCredential(); - userCredentialExample.setUserId(user.getId()); - userCredentialExample.setProvider(CredentialProvider.LOCAL); - - // get userContainer credentials from database - var userCredentials = userCredentialRepository.findOne(Example.of(userCredentialExample)) - .orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请联系管理员处理。")); - - // validate password - if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) { - log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal()); - throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); - } - - // erase credentials - usernamePasswordAuthentication.eraseCredentials(); - - // set values - usernamePasswordAuthentication.setAuthenticated(true); - usernamePasswordAuthentication.setDetails(user); - - return usernamePasswordAuthentication; - } - - /** - * Checks if this provider supports the given authentication type. - * - * @param authentication the authentication class to check - * @return true if this provider supports the given type - */ - @Override - public boolean supports(Class authentication) { - return UsernamePasswordAuthentication.class.isAssignableFrom(authentication); - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java b/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java deleted file mode 100644 index 68f50bd..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/AppService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.manager.AppManager; -import org.springframework.stereotype.Service; - -@Service -public class AppService { - - private final AppManager appManager; - - public AppService(AppManager appManager) { - this.appManager = appManager; - } - - public String getVersion() { - return appManager.getVersion(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java b/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java deleted file mode 100644 index da41902..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.LoginRequest; -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.security.authentication.UsernamePasswordAuthentication; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.stereotype.Service; - -/** - * Service handling user authentication, login, and session management. - * - * @author zihluwang - */ -@Service -public class AuthService { - - private static final Logger log = LoggerFactory.getLogger(AuthService.class); - private final AuthenticationManager authenticationManager; - - public AuthService(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - /** - * Authenticates a user with the given login credentials. - *

- * Delegates authentication to Spring Security's {@link AuthenticationManager} and verifies - * that the result is of the expected {@link UsernamePasswordAuthentication} type. - * - * @param request the login credentials containing principle and password - * @return the authenticated {@link User} - * @throws BizException if authentication fails or the result type is unexpected - */ - public User login(LoginRequest request) { - var _authentication = authenticationManager.authenticate(UsernamePasswordAuthentication - .unauthenticated(request.principle(), request.credential())); - if (!(_authentication instanceof UsernamePasswordAuthentication authentication)) { - log.error( - "Type mismatched, required type is UsernamePasswordAuthentication but got {}.", - _authentication.getClass() - ); - throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "登录服务异常,请稍后再试。"); - } - - return authentication.getDetails(); - } - -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java b/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java deleted file mode 100644 index eb93086..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.manager.CookieManager; -import org.springframework.http.ResponseCookie; -import org.springframework.stereotype.Service; - -import java.time.Duration; - -/** - * Service for building HTTP cookies with configurable properties. - * - * @author zihluwang - */ -@Service -public class CookieService { - - private final CookieManager cookieManager; - - public CookieService(CookieManager cookieManager) { - this.cookieManager = cookieManager; - } - - /** - * Builds a response cookie with the default max age from configuration. - * - * @param cookieName the cookie name - * @param value the cookie value - * @return a configured ResponseCookie - */ - public ResponseCookie buildCookie(String cookieName, String value) { - return buildCookieInternal(cookieName, value, cookieManager.getMaxAge()); - } - - /** - * Builds a response cookie with a custom valid duration. - * - * @param cookieName the cookie name - * @param value the cookie value - * @param validDuration the cookie's max age - * @return a configured ResponseCookie - */ - public ResponseCookie buildCookie(String cookieName, String value, Duration validDuration) { - return buildCookieInternal(cookieName, value, validDuration); - } - - /** - * Creates a response cookie builder with specified name, value and valid duration. - * - * @param name name of the cookie - * @param value value of the cookie - * @param maxAge valid duration of the cookie - * @return cookie builder - */ - protected ResponseCookie buildCookieInternal( - String name, - String value, - Duration maxAge - ) { - return ResponseCookie.from(name, value) - .secure(cookieManager.getSecure()) - .maxAge(maxAge) - .httpOnly(cookieManager.getHttpOnly()) - .path(cookieManager.getPath()) - .sameSite(cookieManager.getSameSite().attributeValue()) - .build(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java b/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java deleted file mode 100644 index fa2118d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse; -import com.onixbyte.deltaforceguide.manager.DailyPasswordManager; -import org.springframework.stereotype.Service; - -/** - * Service for generating and caching daily rotation passwords. - * - * @author zihluwang - */ -@Service -public class DailyPasswordService { - - private final DailyPasswordManager dailyPasswordManager; - - public DailyPasswordService(DailyPasswordManager dailyPasswordManager) { - this.dailyPasswordManager = dailyPasswordManager; - } - - /** - * Retrieves the daily password for the current day. - * @return the daily password response - */ - public DailyPasswordResponse getDailyPassword() { - return dailyPasswordManager.getDailyPassword(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java b/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java deleted file mode 100644 index 5f5098b..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.FirearmRequest; -import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.enumeration.FirearmType; -import com.onixbyte.deltaforceguide.exeption.BizException; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -/** - * Service handling firearm business logic including CRUD operations and queries. - * - * @author zihluwang - */ -@Service -public class FirearmService { - - private final FirearmRepository firearmRepository; - - public FirearmService(FirearmRepository firearmRepository) { - this.firearmRepository = firearmRepository; - } - - /** - * Queries firearms with optional type filter and pagination. - * - * @param type optional firearm type filter - * @param pageable pagination parameters - * @return a paginated response of firearm records - */ - public PageResponse pageQuery(FirearmType type, Pageable pageable) { - Page page = type == null - ? firearmRepository.findAll(pageable) - : firearmRepository.findAllByType(type, pageable); - - return PageResponse.from(page.map(FirearmResponse::from)); - } - - /** - * Finds a firearm by its ID. - * - * @param id the firearm ID - * @return the firearm response - */ - public FirearmResponse queryById(Long id) { - return firearmRepository.findById(id) - .map(FirearmResponse::from) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - } - - /** - * Creates a new firearm from the provided request data. - * - * @param request the firearm creation request - * @return the created firearm response - */ - public FirearmResponse addFirearm(FirearmRequest request) { - var firearm = firearmRepository.save(Firearm.builder() - .name(request.name()) - .type(request.type()) - .level(request.level()) - .calibre(request.calibre()) - .fireRate(request.fireRate()) - .armourDamage(request.armourDamage()) - .bodyDamage(request.bodyDamage()) - .review(request.review()) - .build()); - - return FirearmResponse.from(firearm); - } - - /** - * Updates an existing firearm identified by ID. - * - * @param id the firearm ID - * @param request the updated firearm data - * @return the updated firearm response - */ - public FirearmResponse updateFirearm(Long id, FirearmRequest request) { - var firearm = firearmRepository.findById(id) - .orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - - firearm.setName(request.name()); - firearm.setType(request.type()); - firearm.setLevel(request.level()); - firearm.setCalibre(request.calibre()); - firearm.setFireRate(request.fireRate()); - firearm.setArmourDamage(request.armourDamage()); - firearm.setBodyDamage(request.bodyDamage()); - firearm.setReview(request.review()); - - return FirearmResponse.from(firearmRepository.save(firearm)); - } - - /** - * Deletes a firearm by its ID. - * - * @param id the firearm ID to delete - */ - public void deleteFirearm(Long id) { - Firearm firearm = firearmRepository.findById(id) - .orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); - firearmRepository.delete(firearm); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java deleted file mode 100644 index 9b97e8f..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest; -import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; -import com.onixbyte.deltaforceguide.domain.dto.PageResponse; -import com.onixbyte.deltaforceguide.domain.dto.TuningRequest; -import com.onixbyte.deltaforceguide.domain.entity.Accessory; -import com.onixbyte.deltaforceguide.domain.entity.Firearm; -import com.onixbyte.deltaforceguide.domain.entity.Modification; -import com.onixbyte.deltaforceguide.domain.entity.Tuning; -import com.onixbyte.deltaforceguide.manager.ModificationManager; -import com.onixbyte.deltaforceguide.repository.FirearmRepository; -import com.onixbyte.deltaforceguide.repository.ModificationRepository; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Service handling modification business logic including CRUD, batch operations, and tag filtering. - * - * @author zihluwang - */ -@Service -public class ModificationService { - - private final ModificationRepository modificationRepository; - private final FirearmRepository firearmRepository; - private final ModificationManager modificationManager; - private final ObjectMapper objectMapper; - - public ModificationService( - ModificationRepository modificationRepository, - FirearmRepository firearmRepository, - ModificationManager modificationManager, - ObjectMapper objectMapper - ) { - this.modificationRepository = modificationRepository; - this.firearmRepository = firearmRepository; - this.modificationManager = modificationManager; - this.objectMapper = objectMapper; - } - - /** - * Queries modifications with optional firearm and tag filters. - * - * @param firearmId optional firearm ID filter - * @param tags optional tag list filter - * @param pageable pagination parameters - * @return a paginated response of modification records - */ - public PageResponse pageQuery(Long firearmId, List tags, Pageable pageable) { - String tagsJson = null; - if (tags != null && !tags.isEmpty()) { - try { - tagsJson = objectMapper.writeValueAsString(tags); - } catch (JsonProcessingException e) { - throw new RuntimeException("Failed to serialize tags", e); - } - } - - Page page; - if (tagsJson != null || firearmId != null) { - page = modificationRepository.pageQueryByFirearmAndTags(firearmId, tagsJson, pageable); - } else { - page = modificationRepository.findAllBy(pageable); - } - - return PageResponse.from(page.map(ModificationResponse::from)); - } - - /** - * Finds a modification by its ID. - * - * @param id the modification ID - * @return the modification response - */ - public ModificationResponse queryById(Long id) { - return modificationRepository.findById(id) - .map(ModificationResponse::from) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - } - - /** - * Finds all unique tags across modifications, optionally scoped to a firearm. - * - * @param firearmId optional firearm ID to scope the tag search - * @return list of unique tag strings - */ - public List findAllTags(Long firearmId) { - return modificationRepository.findAllTags(firearmId); - } - - /** - * Creates a new modification for a given firearm. - * - * @param request the modification creation request - * @return the created modification response - */ - public ModificationResponse create(ModificationRequest request) { - return modificationManager.create(request); - } - - /** - * Creates multiple modifications in a single batch operation. - * - * @param requests list of modification creation requests - * @return list of created modification responses - */ - public List batchCreate(List requests) { - return modificationManager.batchCreate(requests); - } - - /** - * Updates an existing modification identified by ID. - * - * @param id the modification ID - * @param request the updated modification data - * @return the updated modification response - */ - public ModificationResponse update(Long id, ModificationRequest request) { - Modification modification = modificationRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - Firearm firearm = firearmRepository.findById(request.firearmId()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + request.firearmId())); - - modification.setFirearm(firearm); - modification.setName(request.name()); - modification.setCode(request.code()); - modification.setTags(safeTags(request.tags())); - modification.setAccessories(toAccessories(request.accessories())); - modification.setNote(request.note()); - modification.setAuthor(request.author()); - modification.setVideoUrl(request.videoUrl()); - - return ModificationResponse.from(modificationRepository.save(modification)); - } - - /** - * Deletes a modification by its ID. - * - * @param id the modification ID to delete - */ - public void delete(Long id) { - Modification modification = modificationRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); - modificationRepository.delete(modification); - } - - /** - * Deletes multiple modifications in a single batch operation. - * - * @param ids list of modification IDs to delete - */ - public void batchDelete(List ids) { - Set uniqueIds = new LinkedHashSet<>(ids); - List modifications = modificationRepository.findAllById(uniqueIds); - - if (modifications.size() != uniqueIds.size()) { - Set foundIds = modifications.stream() - .map(Modification::getId) - .collect(java.util.stream.Collectors.toSet()); - List missingIds = uniqueIds.stream() - .filter(id -> !foundIds.contains(id)) - .toList(); - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + missingIds); - } - - modificationRepository.deleteAllInBatch(modifications); - } - - private List safeTags(List tags) { - return tags == null ? new ArrayList<>() : tags; - } - - private List toAccessories(List accessoryRequests) { - if (accessoryRequests == null) { - return new ArrayList<>(); - } - - return accessoryRequests.stream() - .map(this::toAccessory) - .toList(); - } - - private Accessory toAccessory(AccessoryRequest request) { - Accessory accessory = new Accessory(); - accessory.setSlotName(request.slotName()); - accessory.setAccessoryName(request.accessoryName()); - accessory.setTunings(toTunings(request.tunings())); - return accessory; - } - - private List toTunings(List tuningRequests) { - if (tuningRequests == null) { - return new ArrayList<>(); - } - - return tuningRequests.stream() - .map(this::toTuning) - .toList(); - } - - private Tuning toTuning(TuningRequest request) { - Tuning tuning = new Tuning(); - tuning.setTuningName(request.tuningName()); - tuning.setTuningValue(request.tuningValue()); - return tuning; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java b/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java deleted file mode 100644 index 700e26d..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/UserService.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.entity.User; -import com.onixbyte.deltaforceguide.domain.entity.UserCredential; -import com.onixbyte.deltaforceguide.manager.UserCredentialManager; -import com.onixbyte.deltaforceguide.manager.UserManager; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.util.List; - -/** - * Service for user account management and profile operations. - * - * @author zihluwang - */ -@Service -public class UserService { - - private final UserManager userManager; - private final UserCredentialManager userCredentialManager; - - public UserService(UserManager userManager, UserCredentialManager userCredentialManager) { - this.userManager = userManager; - this.userCredentialManager = userCredentialManager; - } - - /** - * Retrieves all registered users. - * - * @return list of all users - */ - public List findAll() { - return userManager.findAll(); - } - - /** - * Finds a user by their ID. - * - * @param id the user ID - * @return the user - */ - public User queryById(Long id) { - return userManager.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + id)); - } - - /** - * Finds a user by their username. - * - * @param username the username to search for - * @return the user - */ - public User queryByUsername(String username) { - return userManager.findByUsername(username) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + username)); - } - - /** - * Creates a new user account. - * - * @param user the user entity to persist - * @return the saved user entity - */ - public User create(User user) { - return userManager.save(user); - } - - /** - * Updates an existing user account. - * - * @param user the user entity with updated fields - * @return the saved user entity - */ - public User update(User user) { - if (user.getId() == null || userManager.findById(user.getId()).isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + user.getId()); - } - return userManager.save(user); - } - - /** - * Finds all credentials associated with a user. - * - * @param userId the user ID - * @return list of user credentials - */ - public List findCredentials(Long userId) { - ensureUserExists(userId); - return userCredentialManager.findAllByUserId(userId); - } - - /** - * Queries a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider - * @return the matching credential - */ - public UserCredential queryCredential(Long userId, String provider) { - ensureUserExists(userId); - return userCredentialManager.findByUserIdAndProvider(userId, provider) - .orElseThrow(() -> new ResponseStatusException( - HttpStatus.NOT_FOUND, - "User credential not found: userId=" + userId + ", provider=" + provider)); - } - - /** - * Creates or updates a credential for a user and provider. - * - * @param userId the user ID - * @param provider the authentication provider - * @param credential the credential value - * @return the saved credential - */ - public UserCredential upsertCredential(Long userId, String provider, String credential) { - User user = ensureUserExists(userId); - UserCredential userCredential = userCredentialManager.findByUserIdAndProvider(userId, provider) - .orElseGet(UserCredential::new); - userCredential.setUser(user); - userCredential.setProvider(provider); - userCredential.setCredential(credential); - return userCredentialManager.save(userCredential); - } - - /** - * Deletes a specific credential for a user by provider. - * - * @param userId the user ID - * @param provider the authentication provider - */ - public void deleteCredential(Long userId, String provider) { - ensureUserExists(userId); - userCredentialManager.deleteByUserIdAndProvider(userId, provider); - } - - /** - * Deletes a user and all associated credentials. - * - * @param id the user ID to delete - */ - public void deleteById(Long id) { - ensureUserExists(id); - userCredentialManager.deleteAllByUserId(id); - userManager.deleteById(id); - } - - private User ensureUserExists(Long userId) { - return userManager.findById(userId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + userId)); - } -} - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java b/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java deleted file mode 100644 index e864a77..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.onixbyte.deltaforceguide.service; - -import com.onixbyte.deltaforceguide.domain.dto.*; -import com.onixbyte.deltaforceguide.manager.ModificationManager; -import com.onixbyte.deltaforceguide.manager.WebhookManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import com.onixbyte.deltaforceguide.exeption.BizException; -import org.yaml.snakeyaml.Yaml; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -@Service -public class WebhookService { - - private static final Logger log = LoggerFactory.getLogger(WebhookService.class); - private static final String TRIGGER_LABEL = "weapon-mod"; - private static final Duration DEDUP_TTL = Duration.ofHours(12); - private static final Pattern YAML_FENCE = - Pattern.compile("```ya?ml\\s*\\R(.*?)```", Pattern.DOTALL); - - private final ModificationManager modificationManager; - private final RedisTemplate redisTemplate; - private final WebhookManager webhookManager; - private final Yaml yaml; - - public WebhookService( - ModificationManager modificationManager, - RedisTemplate redisTemplate, - WebhookManager webhookManager - ) { - this.modificationManager = modificationManager; - this.redisTemplate = redisTemplate; - this.webhookManager = webhookManager; - this.yaml = new Yaml(); - } - - public void processIssueEvent(GitHubIssueRequest request) { - var issue = request.issue(); - var repository = request.repository(); - var sender = request.sender(); - - if (!isAllowedSender(sender)) { - log.info("Issue #{} sender={} not in allowed-users, skipping", - issue.number(), sender != null ? sender.login() : "null"); - return; - } - - if (!hasTriggerLabel(issue.labels())) { - log.debug("Issue #{} lacks trigger label, skipping", issue.number()); - return; - } - - var dedupKey = "github:webhook:processed:%s:%d" - .formatted(repository.fullName(), issue.number()); - var acquired = redisTemplate.opsForValue() - .setIfAbsent(dedupKey, "1", DEDUP_TTL); - if (acquired == null || !acquired) { - log.info("Issue #{} already processed, skipping", issue.number()); - return; - } - - var parsedYaml = extractYaml(issue.body()); - if (parsedYaml == null) { - log.warn("No YAML block found in issue #{} body", issue.number()); - return; - } - - var data = yaml.>load(parsedYaml); - if (data == null) { - log.warn("Empty YAML block in issue #{}", issue.number()); - return; - } - - if (data.containsKey("modifications")) { - processBatch(issue.number(), data); - } else { - processSingle(issue.number(), data); - } - } - - private void processSingle(Long issueNumber, Map data) { - var request = mapToRequest(data); - log.info("Creating modification from issue #{}: name={}", issueNumber, request.name()); - modificationManager.create(request); - } - - @SuppressWarnings("unchecked") - private void processBatch(Long issueNumber, Map data) { - var list = (List>) data.get("modifications"); - if (list == null || list.isEmpty()) { - log.warn("Empty modifications list in issue #{}", issueNumber); - return; - } - var requests = list.stream() - .map(this::mapToRequest) - .toList(); - log.info("Batch creating {} modifications from issue #{}", requests.size(), issueNumber); - modificationManager.batchCreate(requests); - } - - private ModificationRequest mapToRequest(Map data) { - Long firearmId = modificationManager.resolveFirearmId( - toLong(data.get("firearmId")), - (String) data.get("firearmName")); - if (firearmId == null) { - throw new BizException(HttpStatus.BAD_REQUEST, - "YAML must contain firearmId or firearmName"); - } - String name = (String) data.get("name"); - String code = (String) data.get("code"); - List tags = toStringList(data.get("tags")); - List accessories = mapAccessories(data.get("accessories")); - String note = (String) data.get("note"); - String author = (String) data.get("author"); - String videoUrl = (String) data.get("videoUrl"); - - return new ModificationRequest(firearmId, name, code, tags, accessories, - note, author, videoUrl); - } - - private List mapAccessories(Object raw) { - if (!(raw instanceof List list)) { - return new ArrayList<>(); - } - var result = new ArrayList(); - for (var item : list) { - if (item instanceof Map map) { - result.add(new AccessoryRequest( - (String) map.get("slotName"), - (String) map.get("accessoryName"), - mapTunings(map.get("tunings")) - )); - } - } - return result; - } - - private List mapTunings(Object raw) { - if (!(raw instanceof List list)) { - return new ArrayList<>(); - } - var result = new ArrayList(); - for (var item : list) { - if (item instanceof Map map) { - result.add(new TuningRequest( - (String) map.get("tuningName"), - toDouble(map.get("tuningValue")) - )); - } - } - return result; - } - - private List toStringList(Object raw) { - if (raw instanceof List list) { - return list.stream() - .filter(String.class::isInstance) - .map(String.class::cast) - .toList(); - } - return new ArrayList<>(); - } - - private boolean isAllowedSender( - GitHubWebhookSender sender - ) { - var allowedUsers = webhookManager.allowedUsers(); - if (allowedUsers == null || allowedUsers.isEmpty()) { - return true; - } - if (sender == null || sender.login() == null) { - return false; - } - return allowedUsers.contains(sender.login()); - } - - private boolean hasTriggerLabel(List labels) { - if (labels == null) { - return false; - } - return labels.stream().anyMatch(label -> TRIGGER_LABEL.equals(label.name())); - } - - private String extractYaml(String body) { - if (body == null) { - return null; - } - var matcher = YAML_FENCE.matcher(body); - return matcher.find() ? matcher.group(1) : null; - } - - private Long toLong(Object value) { - if (value instanceof Number num) { - return num.longValue(); - } - if (value instanceof String s) { - return Long.parseLong(s); - } - return null; - } - - private Double toDouble(Object value) { - if (value instanceof Number num) { - return num.doubleValue(); - } - if (value instanceof String s) { - return Double.parseDouble(s); - } - return null; - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java b/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java deleted file mode 100644 index 26f0112..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * Constants for HTTP cookie names used for authentication tokens. - * - * @author zihluwang - */ -public class CookieName { - - public static final String ACCESS_TOKEN = "AccessToken"; -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java b/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java deleted file mode 100644 index 9488536..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * Constants for supported authentication provider identifiers. - * - * @author zihluwang - */ -public class CredentialProvider { - - public static final String LOCAL = "LOCAL"; -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java b/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java deleted file mode 100644 index 134408b..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import java.time.format.DateTimeFormatter; - -/** - * Utility class providing predefined {@link DateTimeFormatter} instances for common date and - * time patterns. These formatters can be used to parse and format {@code java.time} objects - * consistently throughout an application. - * - * @author zihluwang - */ -public class DateTimeFormatters { - - /** - * A {@link DateTimeFormatter} for formatting and parsing full date and time with seconds, using - * the pattern "yyyy-MM-dd HH:mm:ss". - *

- * Example: "{@code 2023-10-27 15:30:45}" - */ - public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing dates only, using the - * pattern "yyyy-MM-dd". - *

- * Example: "{@code 2023-10-27}" - */ - public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing times only, using the - * pattern "HH:mm:ss". - *

- * Example: "{@code 15:30:45}" - */ - public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); - - /** - * A {@link DateTimeFormatter} for formatting and parsing year and month only, using the - * pattern "yyyy-MM". - *

- * Example: "{@code 2023-10}" - */ - public static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java b/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java deleted file mode 100644 index 05b9d58..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -/** - * This class lists the header names that GitHub sends in webhook requests. - * - * @author siujamo - */ -public class GitHubWebhookHeader { - - /** - * The unique identifier of the webhook. - */ - public static final String HOOK_ID = "X-GitHub-Hook-ID"; - - /** - * The name of the event that triggered the delivery. - */ - public static final String EVENT = "X-GitHub-Event"; - - /** - * A globally unique identifier (GUID) to identify the event. - */ - public static final String DELIVERY = "X-GitHub-Delivery"; - - /** - * This header is sent if the webhook is configured with a {@code secret}. This is the HMAC hex - * digest of the request body, and is generated using the SHA-1 hash function and the secret as - * the HMAC {@code key}. {@code X-Hub-Signature} is provided for compatibility with - * existing integrations. We recommend that you use the more secure - * {@code X-Hub-Signature-256} instead. - */ - public static final String SIGNATURE = "X-Hub-Signature"; - - /** - * This header is sent if the webhook is configured with a {@code secret}. This is the HMAC hex - * digest of the request body, and is generated using the SHA-256 hash function and the - * {@code secret} as the HMAC key. For more information, see Validating webhook deliveries. - */ - public static final String SIGNATURE_256 = "X-Hub-Signature-256"; - - /** - * This header will always have the prefix {@code GitHub-Hookshot/}. - */ - public static final String USER_AGENT = "User-Agent"; - - /** - * The type of resource where the webhook was created. - */ - public static final String INSTALLATION_TARGET_TYPE = "X-GitHub-Hook-Installation-Target-Type"; - - /** - * The unique identifier of the resource where the webhook was created. - */ - public static final String INSTALLATION_TARGET_ID = "X-GitHub-Hook-Installation-Target-ID"; -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java b/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java deleted file mode 100644 index ae47823..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - -/** - * Shared Jackson {@link com.fasterxml.jackson.databind.Module} instances for custom date/time - * serialisation and deserialisation across the application. - * - * @author zihluwang - */ -public class JacksonModules { - - public static final SimpleModule DATE_TIME_MODULE = initialiseDateTimeModule(); - - private static SimpleModule initialiseDateTimeModule() { - var javaTimeModule = new JavaTimeModule(); - javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatters.DATE_TIME_FORMATTER)); - javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatters.DATE_TIME_FORMATTER)); - - javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatters.DATE_FORMATTER)); - javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatters.DATE_FORMATTER)); - - javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatters.TIME_FORMATTER)); - javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatters.TIME_FORMATTER)); - - return javaTimeModule; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java b/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java deleted file mode 100644 index 4e50c38..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.onixbyte.deltaforceguide.shared; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; - -/** - * Singleton serialiser for Redis cache operations using - * {@link GenericJackson2JsonRedisSerializer} with JavaTime module support. - * - * @author zihluwang - */ -public class JacksonRedisSerialiser { - - public static final GenericJackson2JsonRedisSerializer INSTANCE = initialiseSerializer(); - - private static GenericJackson2JsonRedisSerializer initialiseSerializer() { - var serializer = new GenericJackson2JsonRedisSerializer(); - - serializer.configure((configurer) -> { - configurer.registerModule(new JavaTimeModule()); - configurer.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - }); - - return serializer; - } -} - diff --git a/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java b/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java deleted file mode 100644 index 1d576a5..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.onixbyte.deltaforceguide.utils; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * Utility class for date and time operations using system-default time zone. - * - * @author zihluwang - */ -public class DateTimeUtil { - - /** - * Convert a {@link LocalDateTime} to an {@link Instant} using the system-default time zone. - * - * @param ldt the local date-time to convert - * @return the corresponding instant - */ - public static Instant asInstant(LocalDateTime ldt) { - return ldt.atZone(ZoneId.systemDefault()) - .toInstant(); - } -} diff --git a/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java b/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java deleted file mode 100644 index ee31c22..0000000 --- a/src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.onixbyte.deltaforceguide.wrapper; - -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import org.springframework.util.StreamUtils; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper { - - private final byte[] body; - - public RepeatedlyReadRequestWrapper(HttpServletRequest request) throws IOException { - super(request); - this.body = StreamUtils.copyToByteArray(request.getInputStream()); - } - - @Override - public ServletInputStream getInputStream() { - final var byteArrayInputStream = new ByteArrayInputStream(body); - return new ServletInputStream() { - @Override - public boolean isFinished() { - return byteArrayInputStream.available() == 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) { - } - - @Override - public int read() throws IOException { - return byteArrayInputStream.read(); - } - }; - } - - @Override - public BufferedReader getReader() throws IOException { - return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8)); - } - - public String getBodyString() { - return new String(body, StandardCharsets.UTF_8); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml deleted file mode 100644 index 8fcdf2f..0000000 --- a/src/main/resources/application.yaml +++ /dev/null @@ -1,51 +0,0 @@ -spring: - application: - name: delta-force-guide - cache: - type: redis - redis: - time-to-live: PT2H - data: - redis: - repositories: - # Disable redis repositories - enabled: false - jta: - # Disable JTA support - enabled: false - jpa: - properties: - hibernate: - transaction: - jta: - # No need to use distributed transaction manager for 1 datasource. - platform: org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform - hibernate: - ddl-auto: none - open-in-view: false - datasource: - hikari: - minimum-idle: 1 - maximum-pool-size: 10 - flyway: - enabled: true - baseline-on-migrate: true - -mybatis: - configuration: - log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl - map-underscore-to-camel-case: true - type-aliases-package: com.onixbyte.deltaforceguide.domain.entity - type-handlers-package: com.onixbyte.deltaforceguide.mapper.handler - mapper-locations: classpath:/mapper/*.xml - -logging: - level: - org.hibernate: - orm.connections.pooling: off - -app: - common: - version: ${appVersion} - channel: ${channel} - vendor: ${vendor} diff --git a/src/main/resources/db/migration/V2__init.sql b/src/main/resources/db/migration/V2__init.sql deleted file mode 100644 index acbaad1..0000000 --- a/src/main/resources/db/migration/V2__init.sql +++ /dev/null @@ -1,34 +0,0 @@ -DROP TABLE IF EXISTS firearm CASCADE; -DROP TABLE IF EXISTS modification CASCADE; - - -CREATE TABLE IF NOT EXISTS firearm -( - id BIGSERIAL NOT NULL PRIMARY KEY, - name VARCHAR(64) NOT NULL, - type INT NOT NULL, - level VARCHAR(10) NOT NULL, - review TEXT NULL -); - - -CREATE TABLE IF NOT EXISTS modification -( - id BIGSERIAL NOT NULL PRIMARY KEY, - firearm_id BIGINT NOT NULL, - name VARCHAR(64) NOT NULL, - code VARCHAR(64) NOT NULL, - tags JSONB NULL, - note TEXT NULL, - author VARCHAR(64) NULL, - video_url VARCHAR(512) NULL, - CONSTRAINT fk_modification_firearm - FOREIGN KEY (firearm_id) - REFERENCES firearm (id) - ON DELETE CASCADE - ON UPDATE RESTRICT -); - -CREATE INDEX idx_modification_firearm_id ON modification (firearm_id); - - diff --git a/src/main/resources/db/migration/V3__bullet_and_damages.sql b/src/main/resources/db/migration/V3__bullet_and_damages.sql deleted file mode 100644 index ae0fabf..0000000 --- a/src/main/resources/db/migration/V3__bullet_and_damages.sql +++ /dev/null @@ -1,61 +0,0 @@ --- 创建新表 -DROP TABLE IF EXISTS firearm_new; -CREATE TABLE firearm_new -( - id BIGSERIAL NOT NULL, - name VARCHAR(64) NOT NULL, - type INTEGER NOT NULL, - level VARCHAR(10) NOT NULL, - calibre VARCHAR(20) NOT NULL, - fire_rate INTEGER NOT NULL, - armour_damage INTEGER NOT NULL, - body_damage INTEGER NOT NULL, - review TEXT NULL, - CONSTRAINT firearm_new_pkey PRIMARY KEY (id) -); - --- 迁移数据 -INSERT INTO firearm_new(id, name, type, level, calibre, fire_rate, armour_damage, body_damage, - review) -SELECT id, - name, - type, - level, - '', - 0, - 0, - 0, - review -FROM firearm; - --- 处理外键(关键步骤) --- 先删除指向旧表的外键约束 -ALTER TABLE modification - DROP CONSTRAINT fk_modification_firearm; - --- 重命名旧表和索引 -ALTER TABLE firearm - RENAME TO firearm_legacy; -ALTER INDEX firearm_pkey RENAME TO firearm_legacy_pkey; - --- 重命名新表和索引 -ALTER TABLE firearm_new - RENAME TO firearm; -ALTER INDEX firearm_new_pkey RENAME TO firearm_pkey; - --- 重新建立外键,指向新的 firearm 表 -ALTER TABLE modification - ADD CONSTRAINT fk_modification_firearm - FOREIGN KEY (firearm_id) REFERENCES firearm (id); - --- 序列所有权与名称修正 -ALTER SEQUENCE firearm_id_seq RENAME TO firearm_legacy_id_seq; -ALTER SEQUENCE firearm_new_id_seq RENAME TO firearm_id_seq; -ALTER SEQUENCE firearm_id_seq OWNED BY firearm.id; - --- 更新序列计数器 -SELECT setval('firearm_id_seq', coalesce(max(id), 1)) -FROM firearm; - --- 删除旧表 -DROP TABLE IF EXISTS firearm_legacy CASCADE; \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__user.sql b/src/main/resources/db/migration/V4__user.sql deleted file mode 100644 index 0215d26..0000000 --- a/src/main/resources/db/migration/V4__user.sql +++ /dev/null @@ -1,18 +0,0 @@ -DROP TABLE IF EXISTS app_user CASCADE; -CREATE TABLE app_user -( - id BIGSERIAL NOT NULL PRIMARY KEY, - username VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - CONSTRAINT app_user_username_key UNIQUE (username), - CONSTRAINT app_user_email_key UNIQUE (email) -); - -DROP TABLE IF EXISTS app_user_credential CASCADE; -CREATE TABLE app_user_credential -( - user_id BIGINT NOT NULL REFERENCES app_user (id), - provider VARCHAR(255) NOT NULL, - credential VARCHAR(255) NOT NULL, - CONSTRAINT app_user_credential_pkey PRIMARY KEY (user_id, provider) -); \ No newline at end of file diff --git a/src/main/resources/db/migration/V5__modification_accessories.sql b/src/main/resources/db/migration/V5__modification_accessories.sql deleted file mode 100644 index 1450826..0000000 --- a/src/main/resources/db/migration/V5__modification_accessories.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE modification - ADD accessories JSONB NOT NULL DEFAULT '[]'; \ No newline at end of file diff --git a/web/.github/dependabot.yml b/web/.github/dependabot.yml deleted file mode 100644 index 9a810e3..0000000 --- a/web/.github/dependabot.yml +++ /dev/null @@ -1,27 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - commit-message: - prefix: "chore: " - - groups: - dependency-updates: - patterns: ["*"] - update-types: ["minor", "patch"] - - ignore: - - dependency-name: "@types/node" - update-types: ["version-update:semver-major"] - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index e0d19bc..0000000 --- a/web/.gitignore +++ /dev/null @@ -1,288 +0,0 @@ -### macOS -# General -.DS_Store -.AppleDouble -.LSOverride - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Linux -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# Metadata left by Dolphin file manager, which comes with KDE Plasma -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -# Log files created by default by the nohup command -nohup.out - -### Windows - -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### JetBrains IDE -# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea - -# Gradle and Maven with auto-import -*.iml -*.ipr - -# File-based project format -*.iws - -# IntelliJ -out/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based HTTP Client -http-client.private.env.json - -### Gradle -.gradle -**/build/ -!**/src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Avoid ignore Gradle wrappper properties -!gradle-wrapper.properties - -# Cache of project -.gradletasknamecache - -# Eclipse Gradle plugin generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -### Java -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -### Node.js -# Logs -logs -#*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.* -!.env.example - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -#.cache - -# Sveltekit cache directory -.svelte-kit/ - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# Firebase cache directory -.firebase/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v3 -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Vite logs files -vite.config.js.timestamp-* -vite.config.ts.timestamp-* \ No newline at end of file diff --git a/web/LICENCE b/web/LICENCE deleted file mode 100644 index f88ade4..0000000 --- a/web/LICENCE +++ /dev/null @@ -1,21 +0,0 @@ -MIT Licence - -Copyright (c) 2026 Zihlu Wang - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/web/README.md b/web/README.md deleted file mode 100644 index cfc785a..0000000 --- a/web/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Delta Force Firearm Modification Codes - -Delta Force Firearm Modification Codes is a lightweight web app for browsing and filtering firearm modification codes for Delta Force. - -The site is built with Vite, React, TypeScript, Tailwind CSS, and React Router. It presents a searchable code library with filtering by weapon, mode, and tag, and includes quick copy support for each modification code. - -## Features - -- Browse a curated list of firearm modification codes. -- Filter results by weapon, mode, and tag. -- Copy modification codes directly from the interface. -- Render large lists efficiently with window virtualisation. -- Deploy as a static site. - -## Tech Stack - -- Vite -- React 19 -- TypeScript -- Tailwind CSS 4 -- React Router 7 -- @tanstack/react-virtual -- Day.js - -## Getting Started - -### Prerequisites - -- Node.js 20 or later is recommended. -- pnpm is required for dependency management and scripts. - -### Install dependencies - -```bash -pnpm install -``` - -### Start the development server - -```bash -pnpm dev -``` - -### Build for production - -```bash -pnpm build -``` - -### Preview the production build locally - -```bash -pnpm preview -``` - -## Available Scripts - -- `pnpm dev`: start the Vite development server. -- `pnpm build`: run TypeScript compilation and create a production build. -- `pnpm preview`: preview the production bundle locally. -- `pnpm lint`: run project linting. -- `pnpm deploy`: build and publish the site with `gh-pages`. - -## Project Structure - -```text -src/ - components/ Shared UI components - data/ Modification code dataset - init/ Application initialisation - layout/ Route layouts - page/ Route pages - router/ Router configuration -``` - -The current dataset is stored in `src/data/modification-codes.json`. - -## Deployment - -The repository is configured for static deployment. The `public/CNAME` file indicates the site is intended to be served on `onixbyte.dev`. - -To deploy: - -```bash -pnpm deploy -``` - -## Contributing - -Contributions are welcome. If you want to improve the dataset, refine the filtering experience, or fix UI issues, open an issue or submit a pull request. - -When contributing, please keep documentation and user-facing copy in British English. - -## Licence - -This project is released under the MIT Licence. See `LICENCE` for details. \ No newline at end of file diff --git a/web/src/init/dayjs/index.ts b/web/src/init/dayjs/index.ts deleted file mode 100644 index 35a84ed..0000000 --- a/web/src/init/dayjs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import dayjs from "dayjs" -import duration from "dayjs/plugin/duration" - -dayjs.extend(duration) - -console.log("Global Dayjs plugins initialised.") \ No newline at end of file diff --git a/web/src/init/index.ts b/web/src/init/index.ts deleted file mode 100644 index fc6e552..0000000 --- a/web/src/init/index.ts +++ /dev/null @@ -1 +0,0 @@ -import "./dayjs" \ No newline at end of file