[+] Wacca database models

This commit is contained in:
Azalea
2024-03-25 03:06:28 -04:00
parent 89461893a4
commit 484bb758ae
8 changed files with 510 additions and 48 deletions

View File

@@ -83,45 +83,6 @@ operator fun Int.minus(message: String): Nothing {
val emailRegex = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$".toRegex()
fun Str.isValidEmail(): Bool = emailRegex.matches(this)
// JSON
val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null")
val ACCEPTABLE_TRUE = setOf("1", "true", "yes", "on", "True")
val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : JsonDeserializer<Boolean>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext) = when(parser.text) {
in ACCEPTABLE_FALSE -> false
in ACCEPTABLE_TRUE -> true
else -> 400 - "Invalid boolean value ${parser.text}"
}
})
val JSON_DATETIME = SimpleModule().addDeserializer(LocalDateTime::class.java, object : JsonDeserializer<LocalDateTime>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
parser.text.asDateTime() ?: (400 - "Invalid date time value ${parser.text}")
})
val JACKSON = ObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)
setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
findAndRegisterModules()
registerModule(JSON_FUZZY_BOOLEAN)
registerModule(JSON_DATETIME)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
inline fun <reified T> ObjectMapper.readValue(str: Str) = readValue(str, T::class.java)
// TODO: https://stackoverflow.com/q/78197784/7346633
inline fun <reified T> Str.parseJackson() = if (contains("null")) {
val map = JACKSON.readValue<MutableMap<String, Any>>(this)
JACKSON.convertValue(map.recursiveNotNull(), T::class.java)
}
else JACKSON.readValue(this, T::class.java)
fun <T> T.toJson() = JACKSON.writeValueAsString(this)
@OptIn(ExperimentalSerializationApi::class)
val JSON = Json {
ignoreUnknownKeys = true
isLenient = true
namingStrategy = JsonNamingStrategy.SnakeCase
explicitNulls = false
coerceInputValues = true
}
inline fun <reified T> Json.parse(str: Str) = decodeFromString<T>(str)
// Global Tools
val HTTP = HttpClient(CIO) {
install(ContentNegotiation) {

53
src/main/java/ext/Json.kt Normal file
View File

@@ -0,0 +1,53 @@
package ext
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
// Jackson
val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null")
val ACCEPTABLE_TRUE = setOf("1", "true", "yes", "on", "True")
val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : JsonDeserializer<Boolean>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext) = when(parser.text) {
in ACCEPTABLE_FALSE -> false
in ACCEPTABLE_TRUE -> true
else -> 400 - "Invalid boolean value ${parser.text}"
}
})
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<java.time.LocalDateTime>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
parser.text.asDateTime() ?: (400 - "Invalid date time value ${parser.text}")
})
val JACKSON = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)
setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
findAndRegisterModules()
registerModule(JSON_FUZZY_BOOLEAN)
registerModule(JSON_DATETIME)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
}
inline fun <reified T> ObjectMapper.readValue(str: Str) = readValue(str, T::class.java)
// TODO: https://stackoverflow.com/q/78197784/7346633
inline fun <reified T> Str.parseJackson() = if (contains("null")) {
val map = JACKSON.readValue<MutableMap<String, Any>>(this)
JACKSON.convertValue(map.recursiveNotNull(), T::class.java)
}
else JACKSON.readValue(this, T::class.java)
fun <T> T.toJson() = JACKSON.writeValueAsString(this)
// KotlinX Serialization
@OptIn(ExperimentalSerializationApi::class)
val JSON = Json {
ignoreUnknownKeys = true
isLenient = true
namingStrategy = JsonNamingStrategy.SnakeCase
explicitNulls = false
coerceInputValues = true
}

View File

@@ -3,9 +3,11 @@ package icu.samnyan.aqua.net.games
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import ext.JACKSON
import ext.JavaSerializable
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
import jakarta.persistence.*
import kotlinx.serialization.Serializable
import org.springframework.data.domain.Page
@@ -104,7 +106,6 @@ interface IGenericGamePlaylog {
val isAllPerfect: Boolean
}
@Serializable
@MappedSuperclass
open class BaseEntity(
@Id
@@ -115,6 +116,15 @@ open class BaseEntity(
override fun toString() = JACKSON.writeValueAsString(this)
}
@MappedSuperclass
open class UserDataEntity : BaseEntity() {
@JsonSerialize(using = AccessCodeSerializer::class)
@JsonProperty(value = "accessCode", access = JsonProperty.Access.READ_ONLY)
@OneToOne
@JoinColumn(name = "aime_card_id", unique = true)
var card: Card? = null
}
@NoRepositoryBean
interface GenericUserDataRepo<T : IGenericUserData> : JpaRepository<T, Long> {
fun findByCard(card: Card): T?

View File

@@ -3,8 +3,6 @@ package icu.samnyan.aqua.sega.general.model
import com.fasterxml.jackson.annotation.JsonIgnore
import icu.samnyan.aqua.net.db.AquaNetUser
import jakarta.persistence.*
import java.io.Serial
import java.io.Serializable
import java.time.LocalDateTime
/**
@@ -43,12 +41,7 @@ class Card(
// Whether the card is a ghost card
@Column(name = "is_ghost")
var isGhost: Boolean = false,
): Serializable {
companion object {
@Serial
private val serialVersionUID = 1L
}
) {
@Suppress("unused") // Used by serialization
val isLinked get() = aquaUser != null
}

View File

@@ -0,0 +1,30 @@
package icu.samnyan.aqua.sega.wacca.model.db
import jakarta.transaction.Transactional
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean
interface WcUserRepo : JpaRepository<WaccaUser, Long> {
fun findByCardExtId(extId: Long): WaccaUser?
}
@NoRepositoryBean
interface IWaccaUserLinked<T> : JpaRepository<T, Long> {
fun findByUser(user: WaccaUser): List<T>
fun findByUserCardExtId(userId: Long): List<T>
@Transactional
fun deleteByUser(user: WaccaUser)
}
interface WcUserOptionRepo : IWaccaUserLinked<WcUserOption>
interface WcUserBingoRepo : IWaccaUserLinked<WcUserBingo>
interface WcUserFriendRepo : IWaccaUserLinked<WcUserFriend>
interface WcUserFavoriteSongRepo : IWaccaUserLinked<WcUserFavoriteSong>
interface WcUserGateRepo : IWaccaUserLinked<WcUserGate>
interface WcUserItemRepo : IWaccaUserLinked<WcUserItem>
interface WcUserTicketRepo : IWaccaUserLinked<WcUserTicket>
interface WcUserSongUnlockRepo : IWaccaUserLinked<WcUserSongUnlock>
interface WcUserTrophyRepo : IWaccaUserLinked<WcUserTrophy>
interface WcUserBestScoreRepo : IWaccaUserLinked<WcUserScore>
interface WcUserPlayLogRepo : IWaccaUserLinked<WcUserPlayLog>
interface WcUserStageUpRepo : IWaccaUserLinked<WcUserStageUp>

View File

@@ -0,0 +1,57 @@
package icu.samnyan.aqua.sega.wacca.model.db
import icu.samnyan.aqua.net.games.BaseEntity
import icu.samnyan.aqua.sega.general.model.Card
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.JoinColumn
import jakarta.persistence.OneToOne
import jakarta.persistence.Table
/**
* General user information
*/
@Entity @Table(name = "wacca_user")
class WaccaUser : BaseEntity() {
@OneToOne
@JoinColumn(name = "aime_card_id", unique = true)
var card: Card? = null
@Column(length = 8)
var username = ""
var xp = 0
var wp = 0
var wpTotal = 0
var wpSpent = 0
var danType = 0
var danLevel = 0
var title0 = 0
var title1 = 0
var title2 = 0
var rating = 0
var vipExpireTime: String? = null
var alwaysVip = false
var loginCount = 0
var loginCountConsec = 0
var loginCountDays = 0
var loginCountDaysConsec = 0
var loginCountToday = 0
var playcountSingle = 0
var playcountMultiVs = 0
var playcountMultiCoop = 0
var playcountStageup = 0
var playcountTimeFree = 0
var friendView1 = 0
var friendView2 = 0
var friendView3 = 0
@Column(length = 50)
var lastGameVer = ""
var lastSongId = 0
var lastSongDifficulty = 0
var lastFolderOrder = 0
var lastFolderId = 0
var lastSongOrder = 0
var lastLoginDate: String? = null
var gateTutorialFlags: String? = null
}

View File

@@ -0,0 +1,147 @@
package icu.samnyan.aqua.sega.wacca.model.db
import com.fasterxml.jackson.annotation.JsonIgnore
import icu.samnyan.aqua.net.games.BaseEntity
import jakarta.persistence.*
typealias UC = UniqueConstraint
/**
* Base entity for all wacca user-related entities
*/
@MappedSuperclass
open class WaccaUserEntity : BaseEntity() {
@JsonIgnore
@ManyToOne
@JoinColumn(name = "user_id")
open var user: WaccaUser = WaccaUser()
}
/**
* In-game option key-value storage
*/
@Entity @Table(name = "wacca_user_option", uniqueConstraints = [UC("", ["user_id", "opt_id"])])
class WcUserOption : WaccaUserEntity() {
var optId = 0
var value = 0
}
@Entity @Table(name = "wacca_user_bingo", uniqueConstraints = [UC("", ["user_id", "page_number"])])
class WcUserBingo : WaccaUserEntity() {
var pageNumber = 0
var pageProgress = ""
}
/**
* The user here is the sender of the friend request.
*/
@Entity @Table(name = "wacca_friend", uniqueConstraints = [UC("", ["user_id", "with"])])
class WcUserFriend : WaccaUserEntity() {
@ManyToOne @JoinColumn(name = "profile_reciever")
var with: WaccaUser = WaccaUser()
var isAccepted = false
}
@Entity @Table(name = "wacca_user_favorite_song", uniqueConstraints = [UC("", ["user_id", "song_id"])])
class WcUserFavoriteSong : WaccaUserEntity() {
// TODO: Make this into a list instead?
var songId = 0
}
@Entity @Table(name = "wacca_user_gate", uniqueConstraints = [UC("", ["user_id", "gate_id"])])
class WcUserGate : WaccaUserEntity() {
var gateId = 0
var page = 0
var progress = 0
var loops = 0
var missionFlag = 0
var totalPoints = 0
}
@Entity @Table(name = "wacca_user_item", uniqueConstraints = [UC("", ["user_id", "item_id", "type"])])
class WcUserItem : WaccaUserEntity() {
var itemId = 0
var type = 0
var acquireDate = ""
var useCount = 0
}
@Entity @Table(name = "wacca_user_ticket", uniqueConstraints = [UC("", ["user_id", "ticket_id"])])
class WcUserTicket : WaccaUserEntity() {
var ticketId = 0
var acquireDate = ""
var expireDate = ""
}
@Entity @Table(name = "wacca_user_song_unlock", uniqueConstraints = [UC("", ["user_id", "song_id"])])
class WcUserSongUnlock : WaccaUserEntity() {
var songId = 0
var highestDifficulty = 0
var acquireDate = ""
}
@Entity @Table(name = "wacca_user_trophy", uniqueConstraints = [UC("", ["user_id", "trophy_id", "season"])])
class WcUserTrophy : WaccaUserEntity() {
var trophyId = 0
var season = 0
var progress = 0
var badgeType = 0
}
@Entity @Table(name = "wacca_user_score", uniqueConstraints = [UC("", ["user_id", "song_id", "chart_id"])])
class WcUserScore : WaccaUserEntity() {
var songId = 0
var chartId = 0
var score = 0
var playCt = 0
var clearCt = 0
var misslessCt = 0
var fullcomboCt = 0
var allmarvCt = 0
var gradeDCt = 0
var gradeCCt = 0
var gradeBCt = 0
var gradeACt = 0
var gradeAACt = 0
var gradeAAACt = 0
var gradeSCt = 0
var gradeSSCt = 0
var gradeSSSCt = 0
var gradeMasterCt = 0
var gradeSpCt = 0
var gradeSspCt = 0
var gradeSsspCt = 0
var bestCombo = 0
var lowestMissCt = 0
var rating = 0
}
@Entity @Table(name = "wacca_user_playlog", uniqueConstraints = [UC("", ["user_id", "song_id", "chart_id", "date_scored"])])
class WcUserPlayLog : WaccaUserEntity() {
var songId = 0
var chartId = 0
var score = 0
var clear = 0
var grade = 0
var maxCombo = 0
var marvCt = 0
var greatCt = 0
var goodCt = 0
var missCt = 0
var fastCt = 0
var lateCt = 0
var season = 0
var dateScored = ""
}
@Entity @Table(name = "wacca_user_stageup", uniqueConstraints = [UC("", ["user_id", "stage_id"])])
class WcUserStageUp : WaccaUserEntity() {
var version = 0
var stageId = 0
var clearStatus = 0
var clearSongCt = 0
var song1Score = 0
var song2Score = 0
var song3Score = 0
var playCt = 0
}