From 27bba9525036fa45d0068f07567ef366355ac71d Mon Sep 17 00:00:00 2001 From: siujamo Date: Fri, 28 Aug 2026 10:01:39 +0800 Subject: [PATCH] chore: remove stray server files from root directory on main branch --- .dockerignore | 6 - Dockerfile | 10 - Dockerfile.ci | 19 -- build.gradle.kts | 78 ------ config/application-prod.yaml.example | 57 ---- gradle/libs.versions.toml | 78 ------ gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 7 - gradlew | 251 ------------------ gradlew.bat | 94 ------- settings.gradle.kts | 14 - .../DeltaForceGuideApplication.java | 18 -- .../deltaforceguide/client/TokenClient.java | 75 ------ .../deltaforceguide/config/AppConfig.java | 35 --- .../deltaforceguide/config/CacheConfig.java | 102 ------- .../deltaforceguide/config/CorsConfig.java | 49 ---- .../deltaforceguide/config/FilterConfig.java | 21 -- .../deltaforceguide/config/JacksonConfig.java | 25 -- .../deltaforceguide/config/MyBatisConfig.java | 15 -- .../config/OpenApiConfiguration.java | 26 -- .../config/SecurityConfig.java | 96 ------- .../config/SpringDataConfig.java | 15 -- .../deltaforceguide/config/WebhookConfig.java | 10 - .../controller/AuthController.java | 66 ----- .../controller/DailyPasswordController.java | 32 --- .../controller/FirearmController.java | 72 ----- .../controller/GitHubWebhookController.java | 43 --- .../controller/GlobalExceptionHandler.java | 24 -- .../controller/ModificationController.java | 101 ------- .../controller/TagController.java | 34 --- .../controller/VersionController.java | 26 -- .../converter/FirearmTypeConverter.java | 26 -- .../domain/dto/AccessoryRequest.java | 25 -- .../domain/dto/AccessoryResponse.java | 27 -- .../domain/dto/DailyPassword.java | 12 - .../domain/dto/DailyPasswordData.java | 19 -- .../domain/dto/DailyPasswordMetadata.java | 12 - .../domain/dto/DailyPasswordResponse.java | 14 - .../domain/dto/ErrorResponse.java | 12 - .../domain/dto/FirearmRequest.java | 20 -- .../domain/dto/FirearmResponse.java | 36 --- .../domain/dto/GitHubIssueRequest.java | 15 -- .../domain/dto/GitHubWebhookIssue.java | 20 -- .../domain/dto/GitHubWebhookLabel.java | 12 - .../domain/dto/GitHubWebhookRepository.java | 12 - .../domain/dto/GitHubWebhookSender.java | 12 - .../domain/dto/LoginRequest.java | 20 -- .../dto/ModificationBatchCreateRequest.java | 18 -- .../dto/ModificationBatchDeleteRequest.java | 18 -- .../domain/dto/ModificationRequest.java | 38 --- .../domain/dto/ModificationResponse.java | 39 --- .../domain/dto/PageResponse.java | 29 -- .../domain/dto/TuningRequest.java | 18 -- .../domain/dto/TuningResponse.java | 21 -- .../domain/dto/UserResponse.java | 26 -- .../domain/entity/Accessory.java | 72 ----- .../domain/entity/Firearm.java | 232 ---------------- .../domain/entity/Modification.java | 211 --------------- .../deltaforceguide/domain/entity/Tuning.java | 50 ---- .../deltaforceguide/domain/entity/User.java | 119 --------- .../domain/entity/UserCredential.java | 143 ---------- .../domain/entity/UserCredentialId.java | 81 ------ .../enumeration/FirearmType.java | 50 ---- .../exeption/BizException.java | 40 --- .../filter/TokenAuthenticationFilter.java | 113 -------- .../deltaforceguide/filter/WebhookFilter.java | 22 -- .../interceptor/GitHubWebhookInterceptor.java | 83 ------ .../interceptor/TrafficInterceptor.java | 46 ---- .../deltaforceguide/manager/AppManager.java | 27 -- .../manager/CookieManager.java | 42 --- .../manager/DailyPasswordManager.java | 85 ------ .../manager/ModificationManager.java | 130 --------- .../manager/UserCredentialManager.java | 76 ------ .../deltaforceguide/manager/UserManager.java | 87 ------ .../manager/WebhookManager.java | 24 -- .../properties/AppProperties.java | 11 - .../properties/CookieProperties.java | 27 -- .../properties/CorsProperties.java | 36 --- .../properties/GitHubWebhookProperties.java | 12 - .../properties/TokenProperties.java | 21 -- .../repository/FirearmRepository.java | 24 -- .../repository/ModificationRepository.java | 65 ----- .../repository/UserCredentialRepository.java | 81 ------ .../repository/UserRepository.java | 51 ---- .../security/annotation/RequiresAuth.java | 19 -- .../UsernamePasswordAuthentication.java | 81 ------ ...sernamePasswordAuthenticationProvider.java | 100 ------- .../deltaforceguide/service/AppService.java | 18 -- .../deltaforceguide/service/AuthService.java | 52 ---- .../service/CookieService.java | 67 ----- .../service/DailyPasswordService.java | 28 -- .../service/FirearmService.java | 111 -------- .../service/ModificationService.java | 219 --------------- .../deltaforceguide/service/UserService.java | 155 ----------- .../service/WebhookService.java | 219 --------------- .../deltaforceguide/shared/CookieName.java | 11 - .../shared/CredentialProvider.java | 11 - .../shared/DateTimeFormatters.java | 46 ---- .../shared/GitHubWebhookHeader.java | 56 ---- .../shared/JacksonModules.java | 40 --- .../shared/JacksonRedisSerialiser.java | 28 -- .../deltaforceguide/utils/DateTimeUtil.java | 24 -- .../wrapper/RepeatedlyReadRequestWrapper.java | 56 ---- src/main/resources/application.yaml | 51 ---- src/main/resources/db/migration/V2__init.sql | 34 --- .../db/migration/V3__bullet_and_damages.sql | 61 ----- src/main/resources/db/migration/V4__user.sql | 18 -- .../V5__modification_accessories.sql | 2 - 108 files changed, 5568 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile delete mode 100644 Dockerfile.ci delete mode 100644 build.gradle.kts delete mode 100644 config/application-prod.yaml.example delete mode 100644 gradle/libs.versions.toml delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 settings.gradle.kts delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplication.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/client/TokenClient.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/AppConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/CacheConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/FilterConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/JacksonConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/MyBatisConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/SpringDataConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/config/WebhookConfig.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/AuthController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/DailyPasswordController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/FirearmController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/VersionController.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/AccessoryResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPassword.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordData.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordMetadata.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/DailyPasswordResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ErrorResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubIssueRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookIssue.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookLabel.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookRepository.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/GitHubWebhookSender.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/LoginRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchCreateRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationBatchDeleteRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningRequest.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/TuningResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/UserResponse.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/Accessory.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/Modification.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/Tuning.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/User.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredential.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/entity/UserCredentialId.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/exeption/BizException.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/filter/TokenAuthenticationFilter.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/filter/WebhookFilter.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/interceptor/GitHubWebhookInterceptor.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/interceptor/TrafficInterceptor.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/AppManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/CookieManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/DailyPasswordManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/ModificationManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/UserCredentialManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/UserManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/manager/WebhookManager.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/properties/AppProperties.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/properties/CookieProperties.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/properties/CorsProperties.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/properties/GitHubWebhookProperties.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/properties/TokenProperties.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/UserCredentialRepository.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/UserRepository.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/security/annotation/RequiresAuth.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/security/authentication/UsernamePasswordAuthentication.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/security/provider/UsernamePasswordAuthenticationProvider.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/AppService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/AuthService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/CookieService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/DailyPasswordService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/FirearmService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/UserService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/WebhookService.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/CookieName.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/CredentialProvider.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/DateTimeFormatters.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/GitHubWebhookHeader.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/JacksonModules.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/shared/JacksonRedisSerialiser.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/utils/DateTimeUtil.java delete mode 100644 src/main/java/com/onixbyte/deltaforceguide/wrapper/RepeatedlyReadRequestWrapper.java delete mode 100644 src/main/resources/application.yaml delete mode 100644 src/main/resources/db/migration/V2__init.sql delete mode 100644 src/main/resources/db/migration/V3__bullet_and_damages.sql delete mode 100644 src/main/resources/db/migration/V4__user.sql delete mode 100644 src/main/resources/db/migration/V5__modification_accessories.sql 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/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 1b33c55baabb587c669f562ae36f953de2481846..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43764 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 '} - 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/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: - *

    - *
  • Custom {@link RedisCacheManager} with JSON serialisation support
  • - *
  • Configured {@link RedisTemplate} for direct Redis operations
  • - *
- * - * @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