[U] Java 21 -> Java 25 (#232)

This commit is contained in:
Azalea
2026-08-02 04:57:41 +00:00
parent 4aba8d8642
commit d34aab37b2
33 changed files with 169 additions and 137 deletions
+8
View File
@@ -0,0 +1,8 @@
*
!gradlew
!gradle/
!gradle/**
!build.gradle.kts
!settings.gradle.kts
!src/
!src/**
+2 -2
View File
@@ -12,9 +12,9 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '21' java-version: '25'
distribution: 'temurin' distribution: 'temurin'
- name: Build with Gradle - name: Build with Gradle
+2 -2
View File
@@ -17,9 +17,9 @@ jobs:
fetch-depth: '10' fetch-depth: '10'
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '25'
distribution: 'temurin' distribution: 'temurin'
server-id: github server-id: github
+4 -4
View File
@@ -1,7 +1,6 @@
# Use a multi-stage build to keep the image size small # Use a multi-stage build to keep the image size small
# Start with a Gradle image for building the project # Pin the builder so ARM64 deployments cannot reuse an older floating jdk25 image.
#FROM gradle:jdk21-alpine as builder FROM gradle:9.6.1-jdk25 AS builder
FROM gradle:8.8.0-jdk21 as builder
# Copy the Gradle wrapper and configuration files separately to leverage Docker cache # Copy the Gradle wrapper and configuration files separately to leverage Docker cache
COPY --chown=gradle:gradle gradlew /home/gradle/ COPY --chown=gradle:gradle gradlew /home/gradle/
@@ -16,6 +15,7 @@ RUN sed -i 's/\r$//' ./gradlew
# Download dependencies - cached if build.gradle.kts and settings.gradle.kts are unchanged # Download dependencies - cached if build.gradle.kts and settings.gradle.kts are unchanged
RUN chmod +x ./gradlew RUN chmod +x ./gradlew
RUN java -version
RUN ./gradlew dependencies RUN ./gradlew dependencies
# Copy the project source, this layer is rebuilt whenever a file has changed # Copy the project source, this layer is rebuilt whenever a file has changed
@@ -25,7 +25,7 @@ COPY --chown=gradle:gradle src /home/gradle/src
RUN ./gradlew build -x test RUN ./gradlew build -x test
# Start with a fresh image for the runtime # Start with a fresh image for the runtime
FROM eclipse-temurin:21-jre-alpine FROM eclipse-temurin:25-jre-alpine
# Set the deployment directory # Set the deployment directory
WORKDIR /app WORKDIR /app
+66 -51
View File
@@ -2,9 +2,10 @@
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
val ktVer = "2.1.10" val ktVer = "2.4.0"
java java
kotlin("jvm") version ktVer kotlin("jvm") version ktVer
@@ -13,9 +14,9 @@ plugins {
kotlin("plugin.serialization") version ktVer kotlin("plugin.serialization") version ktVer
kotlin("plugin.allopen") version ktVer kotlin("plugin.allopen") version ktVer
kotlin("kapt") version ktVer kotlin("kapt") version ktVer
id("org.springframework.boot") version "3.2.3" id("org.springframework.boot") version "4.1.0"
id("com.github.ben-manes.versions") version "0.51.0" id("com.github.ben-manes.versions") version "0.54.0"
id("org.hibernate.orm") version "6.4.4.Final" id("org.hibernate.orm") version "7.4.4.Final"
application application
} }
@@ -26,6 +27,9 @@ repositories {
mavenCentral() mavenCentral()
} }
extra["netty.version"] = "4.2.16.Final"
extra["kotlin-coroutines.version"] = "1.11.0"
dependencies { dependencies {
// Spring boot // Spring boot
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
@@ -34,28 +38,30 @@ dependencies {
exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
} }
implementation("org.springframework.boot:spring-boot-starter-jetty") implementation("org.springframework.boot:spring-boot-starter-jetty")
implementation("io.netty:netty-all") implementation(enforcedPlatform("io.netty:netty-bom:4.2.16.Final"))
implementation("org.apache.commons:commons-lang3:3.14.0") implementation("io.netty:netty-all:4.2.16.Final")
implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.apache.commons:commons-lang3:3.20.0")
implementation("org.flywaydb:flyway-core:10.10.0") implementation("org.apache.httpcomponents.client5:httpclient5:5.6.2")
implementation("org.flywaydb:flyway-mysql:10.10.0") implementation("org.springframework.boot:spring-boot-starter-flyway")
implementation("org.flywaydb:flyway-core:12.10.0")
implementation("org.flywaydb:flyway-mysql:12.10.0")
testImplementation("org.springframework.boot:spring-boot-starter-test") { testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine") exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
} }
testImplementation("org.springframework.security:spring-security-test") testImplementation("org.springframework.security:spring-security-test")
implementation("net.logstash.logback:logstash-logback-encoder:7.4") implementation("net.logstash.logback:logstash-logback-encoder:9.0")
// Metrics // Metrics
implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus") implementation("io.micrometer:micrometer-registry-prometheus")
// Database // Database
runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.3.3") runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.5.9")
runtimeOnly("org.xerial:sqlite-jdbc:3.45.2.0") runtimeOnly("org.xerial:sqlite-jdbc:3.53.2.0")
implementation("org.hibernate.orm:hibernate-core:6.4.4.Final") implementation("org.hibernate.orm:hibernate-core:7.4.4.Final")
implementation("org.hibernate.orm:hibernate-community-dialects:6.4.4.Final") implementation("org.hibernate.orm:hibernate-community-dialects:7.4.4.Final")
implementation("io.github.openfeign.querydsl:querydsl-jpa:6.10.1") implementation("io.github.openfeign.querydsl:querydsl-jpa:7.4.0")
kapt("io.github.openfeign.querydsl:querydsl-apt:6.10.1:jpa") kapt("io.github.openfeign.querydsl:querydsl-apt:7.4.0:jpa")
// JSR305 for nullable // JSR305 for nullable
implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
@@ -65,50 +71,60 @@ dependencies {
// ============================= // =============================
// Network // Network
implementation("io.ktor:ktor-client-core:3.0.3") implementation("io.ktor:ktor-client-core:3.5.1")
implementation("io.ktor:ktor-client-cio:3.0.3") implementation("io.ktor:ktor-client-cio:3.5.1")
implementation("io.ktor:ktor-client-content-negotiation:3.0.3") implementation("io.ktor:ktor-client-content-negotiation:3.5.1")
implementation("io.ktor:ktor-client-encoding:3.0.3") implementation("io.ktor:ktor-client-encoding:3.5.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.3") implementation("io.ktor:ktor-serialization-kotlinx-json:3.5.1")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
// Somehow these are needed for ktor even though they're not in the documentation // Somehow these are needed for ktor even though they're not in the documentation
runtimeOnly("org.reactivestreams:reactive-streams:1.0.4") runtimeOnly("org.reactivestreams:reactive-streams:1.0.4")
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.8.0") runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.11.0")
// Email // Email
implementation("org.simplejavamail:simple-java-mail:8.6.3") implementation("org.simplejavamail:simple-java-mail:9.0.1")
implementation("org.simplejavamail:spring-module:8.6.3") implementation("org.simplejavamail:spring-module:9.0.1")
// GeoIP // GeoIP
implementation("com.maxmind.geoip2:geoip2:4.2.0") implementation("com.maxmind.geoip2:geoip2:5.1.0")
// JWT Authentication // JWT Authentication
implementation("io.jsonwebtoken:jjwt-api:0.12.5") implementation("io.jsonwebtoken:jjwt-api:0.13.0")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.13.0")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.13.0")
// Content validation // Content validation
implementation("org.apache.tika:tika-core:2.9.1") implementation("org.apache.tika:tika-core:3.3.1")
// Import: DateTime Parsing // Import: DateTime Parsing
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.1")
// Serialization // Serialization
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.22.1")
// Testing // Testing
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.1") testImplementation("io.kotest:kotest-runner-junit5-jvm:6.2.2")
testImplementation("io.kotest:kotest-assertions-core") testImplementation("io.kotest:kotest-assertions-core:6.2.2")
} }
group = "icu.samnya" group = "icu.samnya"
version = "1.0.0" version = "1.0.0"
description = "AquaDX Arcade Server" description = "AquaDX Arcade Server"
java.sourceCompatibility = JavaVersion.VERSION_21
java {
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
kotlin { kotlin {
jvmToolchain(21) jvmToolchain(25)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_25)
}
} }
springBoot { springBoot {
@@ -119,14 +135,6 @@ application {
mainClass = "icu.samnyan.aqua.EntryKt" mainClass = "icu.samnyan.aqua.EntryKt"
} }
hibernate {
enhancement {
enableLazyInitialization = true
enableAssociationManagement = false
enableExtendedEnhancement = false
}
}
kapt { kapt {
includeCompileClasspath = false includeCompileClasspath = false
keepJavacAnnotationProcessors = true keepJavacAnnotationProcessors = true
@@ -138,29 +146,36 @@ allOpen {
annotation("jakarta.persistence.Embeddable") annotation("jakarta.persistence.Embeddable")
} }
val buildTime: String by extra(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.of("UTC")).format(Instant.now())) val buildTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.of("UTC")).format(Instant.now())
val projectVersion = version.toString()
extra["buildTime"] = buildTime
tasks.processResources { tasks.processResources {
filesMatching("**/application.properties") { filesMatching("**/application.properties") {
expand(project.properties) expand(
mapOf(
"version" to projectVersion,
"ext" to mapOf("buildTime" to buildTime),
)
)
} }
} }
tasks.test { tasks.test {
enabled = project.hasProperty("runTests") enabled = providers.gradleProperty("runTests").isPresent
useJUnitPlatform() useJUnitPlatform()
jvmArgs("-Dkotest.assertions.collection.print.size=100") jvmArgs("-Dkotest.assertions.collection.print.size=100")
} }
tasks.withType<JavaCompile> { tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
tasks.withType<Javadoc> { tasks.withType<Javadoc>().configureEach {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }
tasks.getByName<Jar>("jar") { tasks.named<Jar>("jar") {
enabled = false enabled = false
} }
@@ -170,12 +185,12 @@ sourceSets {
} }
} }
val copyDependencies by tasks.registering(Copy::class) { val copyDependencies = tasks.register<Copy>("copyDependencies") {
from(configurations.runtimeClasspath) from(configurations.runtimeClasspath)
into("${layout.buildDirectory.get()}/libs/lib") into("${layout.buildDirectory.get()}/libs/lib")
} }
val packageThin by tasks.registering(Jar::class) { tasks.register<Jar>("packageThin") {
group = "build" group = "build"
from(sourceSets.main.get().output) from(sourceSets.main.get().output)
manifest { manifest {
+1 -1
View File
@@ -1,7 +1,7 @@
FROM archlinux:latest FROM archlinux:latest
RUN pacman -Syu --noconfirm \ RUN pacman -Syu --noconfirm \
&& pacman -S --noconfirm openssh sudo jdk21-openjdk wget which procps-ng zsh git curlie micro ripgrep python3 exa \ && pacman -S --noconfirm openssh sudo jdk25-openjdk wget which procps-ng zsh git curlie micro ripgrep python3 exa \
&& rm -rf /var/cache/pacman/pkg/* \ && rm -rf /var/cache/pacman/pkg/* \
&& mkdir -p /var/run/sshd \ && mkdir -p /var/run/sshd \
&& chsh -s /bin/zsh root \ && chsh -s /bin/zsh root \
+2 -2
View File
@@ -41,10 +41,10 @@ docker compose up
``` ```
### Building ### Building
You need to install JDK 21 on your system, then run `./gradlew clean build`. The jar file will be built into the `build/libs` folder. You need to install JDK 25 on your system, then run `./gradlew clean build`. The jar file will be built into the `build/libs` folder.
## Why drop SQLite support? ## Why drop SQLite support?
If you wonder why I dropped SQLite support, ask SQLite devs why they still haven't supported adding a single constraint to a table without all the hassle of creating a new one and migrating all data over and finally deleting the original. If you wonder why I dropped SQLite support, ask SQLite devs why they still haven't supported adding a single constraint to a table without all the hassle of creating a new one and migrating all data over and finally deleting the original.
![](sqlite-sucks.png) ![](sqlite-sucks.png)
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
+4
View File
@@ -2,4 +2,8 @@
* This file was generated by the Gradle 'init' task. * This file was generated by the Gradle 'init' task.
*/ */
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "AquaDX" rootProject.name = "AquaDX"
+1 -1
View File
@@ -277,4 +277,4 @@ fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
joinToString("\n") { it.joinToString(",") } joinToString("\n") { it.joinToString(",") }
// DI // DI
inline fun <reified T> ApplicationContext.lazy() = lazy { getBean(T::class.java) } inline fun <reified T : Any> ApplicationContext.lazy() = kotlin.lazy { getBean(T::class.java) }
-1
View File
@@ -32,7 +32,6 @@ val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::clas
} } } }
}) })
val JACKSON = jacksonObjectMapper().apply { val JACKSON = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)
setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
findAndRegisterModules() findAndRegisterModules()
registerModule(JSON_FUZZY_BOOLEAN) registerModule(JSON_FUZZY_BOOLEAN)
@@ -68,7 +68,7 @@ class CardController(
*/ */
@API("/link") @API("/link")
@Doc("Bind a card to the user. This action will migrate selected data from the card to the user's ghost card.", "Success message") @Doc("Bind a card to the user. This action will migrate selected data from the card to the user's ghost card.", "Success message")
suspend fun link(@RP token: Str, @RP cardId: Str, @RP migrate: Str) = jwt.auth(token) { u -> suspend fun link(@RP token: Str, @RP cardId: Str, @RP migrate: Str): Any = jwt.auth(token) { u ->
// Check if the user's card limit is reached // Check if the user's card limit is reached
if (u.cards.size >= props.linkCardLimit) 400 - "Card limit reached" if (u.cards.size >= props.linkCardLimit) 400 - "Card limit reached"
+1 -1
View File
@@ -154,7 +154,7 @@ class Fedy(
forEach { (k, v) -> v?.let { GAME_OPTIONS_FIELDS[k]?.set(options, it) } } forEach { (k, v) -> v?.let { GAME_OPTIONS_FIELDS[k]?.set(options, it) } }
} }
us.userRepo.save(ru) us.userRepo.save(ru)
if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) } if (fields.containsKey("pwHash")) { us.clearAllSessions(ru) }
UserUpdateRes(user = ru.fedyBasicInfo()) UserUpdateRes(user = ru.fedyBasicInfo())
} caught { UserUpdateRes(error = it) } } caught { UserUpdateRes(error = it) }
} }
@@ -1,12 +1,16 @@
package icu.samnyan.aqua.net.components package icu.samnyan.aqua.net.components
import jakarta.mail.Message
import ext.Bool import ext.Bool
import ext.Str import ext.Str
import ext.logger import ext.logger
import icu.samnyan.aqua.net.db.* import icu.samnyan.aqua.net.db.*
import org.simplejavamail.api.email.Recipient
import org.simplejavamail.api.mailer.Mailer import org.simplejavamail.api.mailer.Mailer
import org.simplejavamail.email.EmailBuilder import org.simplejavamail.email.EmailBuilder
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport
import org.springframework.beans.factory.ObjectProvider
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.boot.context.event.ApplicationStartedEvent
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@@ -25,15 +29,19 @@ class EmailProperties {
var webHost: Str = "aquadx.net" var webHost: Str = "aquadx.net"
} }
@Configuration
@ConditionalOnProperty(prefix = "aqua-net.email", name = ["enable"], havingValue = "true")
@Import(SimpleJavaMailSpringSupport::class)
class EmailMailerConfiguration
/** /**
* Email service. All email related operations should be placed here. * Email service. All email related operations should be placed here.
* *
* Library Documentation: https://www.simplejavamail.org/ * Library Documentation: https://www.simplejavamail.org/
*/ */
@Service @Service
@Import(SimpleJavaMailSpringSupport::class)
class EmailService( class EmailService(
val mailer: Mailer, val mailerProvider: ObjectProvider<Mailer>,
val props: EmailProperties, val props: EmailProperties,
val confirmationRepo: EmailConfirmationRepo, val confirmationRepo: EmailConfirmationRepo,
val resetPasswordRepo: ResetPasswordRepo, val resetPasswordRepo: ResetPasswordRepo,
@@ -44,13 +52,16 @@ class EmailService(
val resetTemplate: Str = this::class.java.getResource("/email/reset.html")?.readText() val resetTemplate: Str = this::class.java.getResource("/email/reset.html")?.readText()
?: throw Exception("Password Reset Template Not Found") ?: throw Exception("Password Reset Template Not Found")
private fun mailer() = mailerProvider.getIfAvailable()
?: throw IllegalStateException("Email is enabled, but Simple Java Mail is not configured")
@Async @Async
@EventListener(ApplicationStartedEvent::class) @EventListener(ApplicationStartedEvent::class)
fun test() { fun test() {
if (!props.enable) return if (!props.enable) return
try { try {
mailer.testConnection() mailer().testConnection()
log.info("Email Service Connected") log.info("Email Service Connected")
} catch (e: Exception) { } catch (e: Exception) {
log.error("Email Service Connection Failed", e) log.error("Email Service Connection Failed", e)
@@ -71,9 +82,9 @@ class EmailService(
// Send email // Send email
log.info("Sending verification email to ${user.email}") log.info("Sending verification email to ${user.email}")
mailer.sendMail(EmailBuilder.startingBlank() mailer().sendMail(EmailBuilder.startingBlank()
.from(props.senderName, props.senderAddr) .from(props.senderName, props.senderAddr)
.to(user.computedName, user.email) .withRecipients(Recipient(user.computedName, user.email, Message.RecipientType.TO, null))
.withSubject("Verify Your Email Address for AquaNet") .withSubject("Verify Your Email Address for AquaNet")
.withHTMLText(confirmTemplate .withHTMLText(confirmTemplate
.replace("{{name}}", user.computedName) .replace("{{name}}", user.computedName)
@@ -94,9 +105,9 @@ class EmailService(
// Send email // Send email
log.info("Sending reset password email to ${user.email}") log.info("Sending reset password email to ${user.email}")
mailer.sendMail(EmailBuilder.startingBlank() mailer().sendMail(EmailBuilder.startingBlank()
.from(props.senderName, props.senderAddr) .from(props.senderName, props.senderAddr)
.to(user.computedName, user.email) .withRecipients(Recipient(user.computedName, user.email, Message.RecipientType.TO, null))
.withSubject("Reset Your Password for AquaNet") .withSubject("Reset Your Password for AquaNet")
.withHTMLText(resetTemplate .withHTMLText(resetTemplate
.replace("{{name}}", user.computedName) .replace("{{name}}", user.computedName)
@@ -108,9 +119,9 @@ class EmailService(
if (!props.enable) return if (!props.enable) return
log.info("Sending test email to $addr") log.info("Sending test email to $addr")
mailer.sendMail(EmailBuilder.startingBlank() mailer().sendMail(EmailBuilder.startingBlank()
.from(props.senderName, props.senderAddr) .from(props.senderName, props.senderAddr)
.to(name, addr) .withRecipients(Recipient(name, addr, Message.RecipientType.TO, null))
.withSubject("Test Email") .withSubject("Test Email")
.withPlainText("This is a test email to check if AquaNet Email Works").buildEmail()).thenRun { .withPlainText("This is a test email to check if AquaNet Email Works").buildEmail()).thenRun {
log.info("Test email sent to $addr") log.info("Test email sent to $addr")
@@ -77,7 +77,7 @@ class GeoIP(
{ {
return try { return try {
val ipa = InetAddress.getByName(ip) val ipa = InetAddress.getByName(ip)
geoLite.country(ipa)?.country?.isoCode ?: "" geoLite.country(ipa)?.country()?.isoCode() ?: ""
} }
catch (e: AddressNotFoundException) { "" } catch (e: AddressNotFoundException) { "" }
catch (e: Exception) { catch (e: Exception) {
@@ -85,4 +85,4 @@ class GeoIP(
"" ""
} }
} }
} }
@@ -221,21 +221,23 @@ class AquaUserServices(
400 - "User with username `$this` already exists" 400 - "User with username `$this` already exists"
} }
fun validateEmail(email: Str) = email.apply { fun validateEmail(email: Str): Str {
// Check if email is valid // Check if email is valid
if (!isValidEmail()) 400 - "Invalid email" if (!email.isValidEmail()) 400 - "Invalid email"
// Check if user with the same email exists // Check if user with the same email exists
if (userRepo.findByEmailIgnoreCase(email) != null) if (userRepo.findByEmailIgnoreCase(email) != null)
400 - "User with email `$email` already exists" 400 - "User with email `$email` already exists"
}
return email
fun checkPwHash(password: Str) = password.run { }
// Validate password
if (length < 8) 400 - "Password must be at least 8 characters" fun checkPwHash(password: Str): Str {
// Validate password
hasher.encode(this) if (password.length < 8) 400 - "Password must be at least 8 characters"
}
return hasher.encode(password) ?: (500 - "Failed to hash password")
}
fun checkDisplayName(displayName: Str) = displayName.apply { fun checkDisplayName(displayName: Str) = displayName.apply {
// Check if display name is valid // Check if display name is valid
@@ -41,7 +41,7 @@ interface IExportClass<UserModel: IUserData> {
} }
@NoRepositoryBean @NoRepositoryBean
interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> { interface IUserRepo<UserModel : IUserData, ThisModel : Any>: JpaRepository<ThisModel, Long> {
fun findByUser(user: UserModel): List<ThisModel> fun findByUser(user: UserModel): List<ThisModel>
fun findSingleByUser(user: UserModel): ThisModel? fun findSingleByUser(user: UserModel): ThisModel?
} }
@@ -96,7 +96,6 @@ class AimeDB(
} }
} }
@Deprecated("Deprecated in Netty 5") // TODO: Move this to ChannelInboundHandler
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
logger.error("AimeDB: Error", cause) logger.error("AimeDB: Error", cause)
ctx.close() ctx.close()
@@ -4,7 +4,8 @@ import ext.logger
import io.netty.bootstrap.ServerBootstrap import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelInitializer import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.MultiThreadIoEventLoopGroup
import io.netty.channel.nio.NioIoHandler
import io.netty.channel.socket.SocketChannel import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.logging.LogLevel import io.netty.handler.logging.LogLevel
@@ -38,7 +39,10 @@ class AimeDbServer(
if (!props.enable) return logger.info("Aime DB is disabled.") if (!props.enable) return logger.info("Aime DB is disabled.")
val bootstrap = ServerBootstrap() val bootstrap = ServerBootstrap()
.group(NioEventLoopGroup(), NioEventLoopGroup()) .group(
MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()),
MultiThreadIoEventLoopGroup(NioIoHandler.newFactory())
)
.handler(LoggingHandler(LogLevel.DEBUG)) .handler(LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel::class.java) .channel(NioServerSocketChannel::class.java)
.childHandler(initializer) .childHandler(initializer)
@@ -11,8 +11,8 @@ import org.eclipse.jetty.util.resource.URLResourceFactory
import org.eclipse.jetty.util.ssl.SslContextFactory import org.eclipse.jetty.util.ssl.SslContextFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer import org.springframework.boot.jetty.JettyServerCustomizer
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory import org.springframework.boot.jetty.servlet.JettyServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@@ -18,7 +18,7 @@ import icu.samnyan.aqua.sega.util.StaticRepo
@NoRepositoryBean @NoRepositoryBean
interface Chu3UserLinked<T> : IUserRepo<Chu3UserData, T> { interface Chu3UserLinked<T : Any> : IUserRepo<Chu3UserData, T> {
fun findByUser_Card_ExtId(extId: Long): List<T> fun findByUser_Card_ExtId(extId: Long): List<T>
fun findSingleByUser_Card_ExtId(extId: Long): T? fun findSingleByUser_Card_ExtId(extId: Long): T?
fun findByUser_Card_ExtId(extId: Long, pageable: Pageable): Page<T> fun findByUser_Card_ExtId(extId: Long, pageable: Pageable): Page<T>
@@ -60,9 +60,9 @@ class PsRankingHandler(val db: DivaRepos) {
score1.add(obj.first.maxScore) score1.add(obj.first.maxScore)
score2.add(obj.second.maxScore) score2.add(obj.second.maxScore)
score3.add(obj.third.maxScore) score3.add(obj.third.maxScore)
name1.add(encode(obj.first.pdId?.playerName ?: "xxx")) name1.add(encode(obj.first.pdId.playerName))
name2.add(encode(obj.second.pdId?.playerName ?: "xxx")) name2.add(encode(obj.second.pdId.playerName))
name3.add(encode(obj.third.pdId?.playerName ?: "xxx")) name3.add(encode(obj.third.pdId.playerName))
} }
return PsRankingResponse( return PsRankingResponse(
@@ -9,7 +9,6 @@ import icu.samnyan.aqua.sega.diva.model.db.userdata.*
import icu.samnyan.aqua.sega.diva.util.DivaCalculator import icu.samnyan.aqua.sega.diva.util.DivaCalculator
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.lang.String
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
@@ -100,7 +99,7 @@ class StageResultHandler(val db: DivaRepos, val calc: DivaCalculator) {
// Calculate reward // Calculate reward
// Contest reward // Contest reward
var contestSpecifier = String.join(",", *request.cr_sp) var contestSpecifier = request.cr_sp.joinToString(",")
val contestRewardType = arrayOf<kotlin.String?>("-1", "-1", "-1") val contestRewardType = arrayOf<kotlin.String?>("-1", "-1", "-1")
val contestRewardValue = arrayOf<kotlin.String?>("-1", "-1", "-1") val contestRewardValue = arrayOf<kotlin.String?>("-1", "-1", "-1")
val contestRewardString1 = arrayOf<kotlin.String?>("***", "***", "***") val contestRewardString1 = arrayOf<kotlin.String?>("***", "***", "***")
@@ -204,10 +203,10 @@ class StageResultHandler(val db: DivaRepos, val calc: DivaCalculator) {
request.cr_cid, request.cr_cid,
request.cr_tv, request.cr_tv,
contestSpecifier, contestSpecifier,
String.join(",", *contestRewardType), contestRewardType.joinToString(","),
String.join(",", *contestRewardValue), contestRewardValue.joinToString(","),
String.join(",", *contestRewardString1), contestRewardString1.joinToString(","),
String.join(",", *contestRewardString2), contestRewardString2.joinToString(","),
contestEntryRewardType, contestEntryRewardType,
contestEntryRewardValue, contestEntryRewardValue,
contestEntryRewardString1, contestEntryRewardString1,
@@ -328,7 +327,7 @@ class StageResultHandler(val db: DivaRepos, val calc: DivaCalculator) {
while (result.size < 60) { while (result.size < 60) {
result.add("-1") result.add("-1")
} }
return String.join(",", result) return result.joinToString(",")
} }
private fun updateReward( private fun updateReward(
@@ -11,7 +11,6 @@ import icu.samnyan.aqua.sega.diva.model.db.gamedata.Contest
import icu.samnyan.aqua.sega.diva.model.db.userdata.PlayerContest import icu.samnyan.aqua.sega.diva.model.db.userdata.PlayerContest
import icu.samnyan.aqua.sega.diva.util.DivaStringUtils import icu.samnyan.aqua.sega.diva.util.DivaStringUtils
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.lang.String
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.Any import kotlin.Any
@@ -47,7 +46,7 @@ class EndHandler(val db: DivaRepos) {
profile.contestNowPlayingId = request.cr_cid profile.contestNowPlayingId = request.cr_cid
profile.contestNowPlayingResultRank = currentResultRank profile.contestNowPlayingResultRank = currentResultRank
profile.contestNowPlayingValue = request.cr_tv profile.contestNowPlayingValue = request.cr_tv
profile.contestNowPlayingSpecifier = String.join(",", *request.cr_sp) profile.contestNowPlayingSpecifier = request.cr_sp.joinToString(",")
} else { } else {
val contestRecord = val contestRecord =
db.contest.findByPdIdAndContestId(profile, request.cr_cid).orElseGet( db.contest.findByPdIdAndContestId(profile, request.cr_cid).orElseGet(
@@ -85,7 +85,7 @@ class Contest : Serializable {
} }
list += listOf(-1, -2, "7fffffffffffffffffffffffffffffff") list += listOf(-1, -2, "7fffffffffffffffffffffffffffffff")
} else { } else {
val groups = pvl!!.split(',').dropLastWhile { it.isEmpty() }.toTypedArray() val groups = pvl.split(',').dropLastWhile { it.isEmpty() }.toTypedArray()
if (groups.size < i) { if (groups.size < i) {
list += listOf(-1, -1, -2, -2, -1, -2, "7fffffffffffffffffffffffffffffff") list += listOf(-1, -1, -2, -2, -1, -2, "7fffffffffffffffffffffffffffffff")
} else { } else {
@@ -98,7 +98,7 @@ class Contest : Serializable {
list.add(this.maxComplexity) list.add(this.maxComplexity)
list.add(-1) list.add(-1)
} else { } else {
val diffList = pvdl!!.split(',').dropLastWhile { it.isEmpty() }.toTypedArray() val diffList = pvdl.split(',').dropLastWhile { it.isEmpty() }.toTypedArray()
if (diffList.size < i) { if (diffList.size < i) {
list.add(this.minComplexity) list.add(this.minComplexity)
list.add(this.maxComplexity) list.add(this.maxComplexity)
@@ -20,7 +20,7 @@ import icu.samnyan.aqua.sega.util.StaticRepo
import java.util.* import java.util.*
@NoRepositoryBean @NoRepositoryBean
interface Mai2UserLinked<T>: JpaRepository<T, Long>, IUserRepo<Mai2UserDetail, T> { interface Mai2UserLinked<T : Any>: JpaRepository<T, Long>, IUserRepo<Mai2UserDetail, T> {
fun findByUser_Card_ExtId(userId: Long): List<T> fun findByUser_Card_ExtId(userId: Long): List<T>
fun findByUser_Card_ExtId(userId: Long, page: Pageable): Page<T> fun findByUser_Card_ExtId(userId: Long, page: Pageable): Page<T>
fun findSingleByUser_Card_ExtId(userId: Long): T? fun findSingleByUser_Card_ExtId(userId: Long): T?
@@ -18,7 +18,7 @@ import java.util.*
@NoRepositoryBean @NoRepositoryBean
interface OngekiUserLinked<T> : IUserRepo<UserData, T> { interface OngekiUserLinked<T : Any> : IUserRepo<UserData, T> {
fun findByUser_Card_ExtId(extId: Long): List<T> fun findByUser_Card_ExtId(extId: Long): List<T>
fun findSingleByUser_Card_ExtId(extId: Long): T? fun findSingleByUser_Card_ExtId(extId: Long): T?
fun findByUser_Card_ExtId(extId: Long, pageable: Pageable): Page<T> fun findByUser_Card_ExtId(extId: Long, pageable: Pageable): Page<T>
@@ -221,4 +221,3 @@ class OngekiRepos(
val u: OngekiUserRepos, val u: OngekiUserRepos,
val g: OngekiGameRepos, val g: OngekiGameRepos,
) )
@@ -14,9 +14,9 @@ interface WcUserRepo : JpaRepository<WaccaUser, Long>, GenericUserDataRepo<Wacca
} }
@NoRepositoryBean @NoRepositoryBean
interface IWaccaUserLinked<T> : JpaRepository<T, Long> { interface IWaccaUserLinked<T : Any> : JpaRepository<T, Long> {
fun findByUser(user: WaccaUser): List<T> fun findByUser(user: WaccaUser): List<T>
fun findByUserCardExtId(userId: Long): List<T> fun findByUserCardExtId(extId: Long): List<T>
@Transactional @Transactional
fun deleteByUser(user: WaccaUser) fun deleteByUser(user: WaccaUser)
} }
@@ -54,4 +54,4 @@ class WaccaRepos(
val bestScore: WcUserBestScoreRepo, val bestScore: WcUserBestScoreRepo,
val playLog: WcUserPlayLogRepo, val playLog: WcUserPlayLogRepo,
val stageUp: WcUserStageUpRepo val stageUp: WcUserStageUpRepo
) )
@@ -33,7 +33,6 @@ class WaccaUser : BaseEntity(), IUserData {
var titles: MutableList<Int> = mutableListOf(0, 0, 0) var titles: MutableList<Int> = mutableListOf(0, 0, 0)
override var playerRating = 0 override var playerRating = 0
override var highestRating = 0 override var highestRating = 0
@Temporal(TemporalType.TIMESTAMP)
var vipExpireTime: Date = Date(0) var vipExpireTime: Date = Date(0)
var alwaysVip = false var alwaysVip = false
var loginCount = 0 var loginCount = 0
@@ -50,11 +49,8 @@ class WaccaUser : BaseEntity(), IUserData {
override var lastRomVersion = "1.0.0" override var lastRomVersion = "1.0.0"
@Convert(converter = IntegerListConverter::class) @Convert(converter = IntegerListConverter::class)
var lastSongInfo: MutableList<Int> = mutableListOf(0, 0, 0, 0, 0) var lastSongInfo: MutableList<Int> = mutableListOf(0, 0, 0, 0, 0)
@Temporal(TemporalType.TIMESTAMP)
var lastConsecDate: Date = Date(0) var lastConsecDate: Date = Date(0)
@Temporal(TemporalType.TIMESTAMP)
override var lastPlayDate: Date = Date() override var lastPlayDate: Date = Date()
@Temporal(TemporalType.TIMESTAMP)
override var firstPlayDate: Date = Date() override var firstPlayDate: Date = Date()
var gateTutorialFlags: String = "[[1, 0], [2, 0], [3, 0], [4, 0], [5, 0]]" var gateTutorialFlags: String = "[[1, 0], [2, 0], [3, 0], [4, 0], [5, 0]]"
@Convert(converter = IntegerListConverter::class) @Convert(converter = IntegerListConverter::class)
@@ -71,4 +67,4 @@ class WaccaUser : BaseEntity(), IUserData {
else vipExpireTime else vipExpireTime
val moddedWp get() = if (card?.aquaUser?.gameOptions?.waccaInfiniteWp == true) 999999 else wp val moddedWp get() = if (card?.aquaUser?.gameOptions?.waccaInfiniteWp == true) 999999 else wp
} }
@@ -56,7 +56,6 @@ class WcUserGate : WaccaUserEntity() {
var progress = 0 var progress = 0
var loops = 0 var loops = 0
@Temporal(TemporalType.TIMESTAMP)
var lastUsed = Date(0) var lastUsed = Date(0)
var missionFlag = 0 var missionFlag = 0
var totalPoints = 0 var totalPoints = 0
@@ -74,7 +73,6 @@ class WcUserItem(
var p2: Long = 0L, var p2: Long = 0L,
var p3: Long = 0L, var p3: Long = 0L,
@Temporal(TemporalType.TIMESTAMP)
var acquiredDate: Date = Date(), var acquiredDate: Date = Date(),
) : WaccaUserEntity() { ) : WaccaUserEntity() {
fun ls() = when (type) { fun ls() = when (type) {
@@ -133,7 +131,6 @@ class WcUserPlayLog : WaccaUserEntity(), IGenericGamePlaylog {
override var beforeRating = 0 override var beforeRating = 0
override var afterRating = 0 override var afterRating = 0
@Temporal(TemporalType.TIMESTAMP)
override var userPlayDate = Date() override var userPlayDate = Date()
fun clears() = ls(1, +isClear, +isFullCombo, +isMissless, +isAllPerfect) fun clears() = ls(1, +isClear, +isFullCombo, +isMissless, +isAllPerfect)
@@ -4,8 +4,8 @@ import icu.samnyan.aqua.sega.aimedb.AimeDbProps
import icu.samnyan.aqua.sega.allnet.AllNetProps import icu.samnyan.aqua.sega.allnet.AllNetProps
import org.apache.hc.client5.http.impl.classic.HttpClients import org.apache.hc.client5.http.impl.classic.HttpClients
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier import org.apache.hc.client5.http.ssl.NoopHostnameVerifier
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder
import org.apache.hc.client5.http.ssl.TrustAllStrategy import org.apache.hc.client5.http.ssl.TrustAllStrategy
import org.apache.hc.core5.ssl.SSLContextBuilder import org.apache.hc.core5.ssl.SSLContextBuilder
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
@@ -71,11 +71,11 @@ class AutoChecker(
val rt = HttpComponentsClientHttpRequestFactory().apply { val rt = HttpComponentsClientHttpRequestFactory().apply {
httpClient = HttpClients.custom() httpClient = HttpClients.custom()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create() .setTlsSocketStrategy(ClientTlsStrategyBuilder.create()
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE) .setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setSslContext(SSLContextBuilder.create() .setSslContext(SSLContextBuilder.create()
.loadTrustMaterial(TrustAllStrategy.INSTANCE) .loadTrustMaterial(TrustAllStrategy.INSTANCE)
.build()).build()).build()).build() .build()).buildClassic()).build()).build()
}.let { RestTemplate(it) } }.let { RestTemplate(it) }
val url = "https://${aimedb.address}:$BILLING_PORT/sys/test" val url = "https://${aimedb.address}:$BILLING_PORT/sys/test"
+6 -6
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="cmp" converterClass="icu.samnyan.aqua.spring.LoggerComponent" /> <conversionRule conversionWord="cmp" class="icu.samnyan.aqua.spring.LoggerComponent" />
<conversionRule conversionWord="cls" converterClass="icu.samnyan.aqua.spring.LoggerClassColor" /> <conversionRule conversionWord="cls" class="icu.samnyan.aqua.spring.LoggerClassColor" />
<conversionRule conversionWord="correlationId" converterClass="org.springframework.boot.logging.logback.CorrelationIdConverter" /> <conversionRule conversionWord="correlationId" class="org.springframework.boot.logging.logback.CorrelationIdConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wex" class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- Define the log file name and path --> <!-- Define the log file name and path -->
<property name="LOG_FILE" value="logs/AquaDX.log"/> <property name="LOG_FILE" value="logs/AquaDX.log"/>