初步實作出刀紀錄表

This commit is contained in:
2026-08-04 21:11:59 +08:00
parent fcb5285788
commit 8c99ba1147
10 changed files with 225 additions and 14 deletions
@@ -0,0 +1,43 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.database.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.bson.Document
import java.time.Instant
@Serializable
data class BattleRecordDto(
val date: String, // yyyy-MM-dd
var uid: String,
@SerialName("player_name") val playerName: String,
@SerialName("discord_name") val discordName: String,
@SerialName("discord_id") val discordID: String,
@SerialName("attack_detail_list") val attackDetailList: List<AttackDetailDto>
) {
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
companion object {
private val json = Json { ignoreUnknownKeys = true }
fun fromDocument(document: Document): BattleRecordDto = json.decodeFromString(document.toJson())
}
}
@Serializable
data class AttackDetailDto(
@SerialName("boss_round") val bossRound: Int, // 周目
@SerialName("boss_index") val bossIndex: Int, // 1~5 號王
val damage: Long, // 傷害
@SerialName("is_compensation") val isCompensation: Boolean = false, // true: 補償刀, false: 正刀
@SerialName("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
@SerialName("updated_at") val updatedAt: Long = Instant.now().toEpochMilli()
) {
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
companion object {
private val json = Json { ignoreUnknownKeys = true }
fun fromDocument(document: Document): AttackDetailDto = json.decodeFromString(document.toJson())
}
}
@@ -1,18 +1,20 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.database.dto package club.sakunadaisuki.pcrediveclanrecordbackend.database.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.bson.Document import org.bson.Document
import java.time.Instant
@Serializable @Serializable
data class MemberDto( data class MemberDto(
var uid: String, var uid: String,
val playerName: String, @SerialName("player_name") val playerName: String,
val nickName: String, @SerialName("discord_name") val discordName: String,
val discordID: String, @SerialName("discord_id") val discordID: String,
var leave: Boolean = false, var leave: Boolean = false,
var createAt: Long? = null, @SerialName("created_at") var createAt: Long? = null,
var updatedAt: Long? = null @SerialName("updated_at") var updatedAt: Long? = null
) { ) {
fun toDocument(): Document = Document.parse(Json.encodeToString(this)) fun toDocument(): Document = Document.parse(Json.encodeToString(this))
@@ -0,0 +1,58 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.database.service
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Filters
import com.mongodb.client.model.Filters.and
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.bson.Document
class BattleRecordService(private val database: MongoDatabase) {
var collection: MongoCollection<Document>
init {
database.createCollection("battle_records")
collection = database.getCollection("battle_records")
}
// Create new car
suspend fun create(data: BattleRecordDto): String = withContext(Dispatchers.IO) {
val doc = data.toDocument()
collection.insertOne(doc)
doc["uid"].toString()
}
// Read a car
suspend fun read(date: String): List<BattleRecordDto> = withContext(Dispatchers.IO) {
collection.find(Filters.eq("date", date)).toList().map(BattleRecordDto::fromDocument)
}
// Read a car
suspend fun read(date: String, uid: String): BattleRecordDto? = withContext(Dispatchers.IO) {
val filter = and(
Filters.eq("date", date),
Filters.eq("uid", uid)
)
collection.find(filter).first()?.let(BattleRecordDto::fromDocument)
}
// Update a car
suspend fun update(date: String, data: BattleRecordDto): Document? = withContext(Dispatchers.IO) {
val filter = and(
Filters.eq("date", date),
Filters.eq("uid", data.uid)
)
collection.findOneAndReplace(filter, data.toDocument())
}
// Delete a car
suspend fun delete(date: String, uid: String): Document? = withContext(Dispatchers.IO) {
val filter = and(
Filters.eq("date", date),
Filters.eq("uid", uid)
)
collection.findOneAndDelete(filter)
}
}
@@ -16,7 +16,7 @@ class MemberService(private val database: MongoDatabase) {
collection = database.getCollection("member") collection = database.getCollection("member")
} }
// Create new car // Create new member
suspend fun create(data: MemberDto): String = withContext(Dispatchers.IO) { suspend fun create(data: MemberDto): String = withContext(Dispatchers.IO) {
data.createAt = System.currentTimeMillis() data.createAt = System.currentTimeMillis()
data.updatedAt = System.currentTimeMillis() data.updatedAt = System.currentTimeMillis()
@@ -26,22 +26,22 @@ class MemberService(private val database: MongoDatabase) {
doc["uid"].toString() doc["uid"].toString()
} }
// Read a car // Read a member
suspend fun read(): List<MemberDto> = withContext(Dispatchers.IO) { suspend fun read(): List<MemberDto> = withContext(Dispatchers.IO) {
collection.find().toList().map(MemberDto::fromDocument) collection.find().toList().map(MemberDto::fromDocument)
} }
// Read a car // Read a member
suspend fun read(uid: String): MemberDto? = withContext(Dispatchers.IO) { suspend fun read(uid: String): MemberDto? = withContext(Dispatchers.IO) {
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument) collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)
} }
// Update a car // Update a member
suspend fun update(uid: String, data: MemberDto): Document? = withContext(Dispatchers.IO) { suspend fun update(uid: String, data: MemberDto): Document? = withContext(Dispatchers.IO) {
collection.findOneAndReplace(Filters.eq("uid", uid), data.toDocument()) collection.findOneAndReplace(Filters.eq("uid", uid), data.toDocument())
} }
// Delete a car // Delete a member
suspend fun delete(uid: String): Document? = withContext(Dispatchers.IO) { suspend fun delete(uid: String): Document? = withContext(Dispatchers.IO) {
collection.findOneAndDelete(Filters.eq("uid", uid)) collection.findOneAndDelete(Filters.eq("uid", uid))
} }
@@ -0,0 +1,24 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.mapper
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.AttackDetailDto
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
import club.sakunadaisuki.pcrediveclanrecordbackend.model.AttackDetail
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecord
fun BattleRecord.toDto(): BattleRecordDto = BattleRecordDto(
date = date,
uid = uid,
playerName = playerName,
discordName = discordName,
discordID = discordID,
attackDetailList = attackDetailList.map { it.toDto() },
)
fun AttackDetail.toDto(): AttackDetailDto = AttackDetailDto(
bossRound = bossRound,
bossIndex = bossIndex,
damage = damage,
isCompensation = isCompensation,
createdAt = createdAt,
updatedAt = updatedAt,
)
@@ -7,7 +7,7 @@ fun Member.toDto(): MemberDto {
return MemberDto( return MemberDto(
uid = uid, uid = uid,
playerName = playerName, playerName = playerName,
nickName = nickName, discordName = discordName,
discordID = discordID, discordID = discordID,
leave = leave, leave = leave,
createAt = createAt, createAt = createAt,
@@ -0,0 +1,21 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.model
import java.time.Instant
data class BattleRecord(
val date: String, // yyyy-MM-dd
var uid: String,
val playerName: String,
val discordName: String,
val discordID: String,
val attackDetailList: List<AttackDetail>
)
data class AttackDetail(
val bossRound: Int, // 周目
val bossIndex: Int, // 1~5 號王
val damage: Long, // 傷害
val isCompensation: Boolean = false, // true: 補償刀, false: 正刀
val createdAt: Long = Instant.now().toEpochMilli(),
val updatedAt: Long = Instant.now().toEpochMilli()
)
@@ -0,0 +1,17 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class BattleRecordRequest(
val date: String,
val uid: String,
@SerialName("player_name") val name: String,
@SerialName("discord_name") val discordName: String,
@SerialName("discord_id") val discordID: String,
val round: Int,
val boss: Int,
val damage: Long,
@SerialName("is_compensation") val isCompensation: Boolean = false,
)
@@ -3,7 +3,7 @@ package club.sakunadaisuki.pcrediveclanrecordbackend.model
data class Member( data class Member(
var uid: String, var uid: String,
val playerName: String, val playerName: String,
val nickName: String, val discordName: String,
val discordID: String, val discordID: String,
var leave: Boolean = false, var leave: Boolean = false,
var createAt: Long? = null, var createAt: Long? = null,
@@ -1,8 +1,12 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
import club.sakunadaisuki.pcrediveclanrecordbackend.database.service.BattleRecordService
import club.sakunadaisuki.pcrediveclanrecordbackend.model.Member import club.sakunadaisuki.pcrediveclanrecordbackend.model.Member
import club.sakunadaisuki.pcrediveclanrecordbackend.database.service.MemberService import club.sakunadaisuki.pcrediveclanrecordbackend.database.service.MemberService
import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toDto import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toDto
import club.sakunadaisuki.pcrediveclanrecordbackend.model.AttackDetail
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecordRequest
import com.mongodb.client.* import com.mongodb.client.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
@@ -17,20 +21,23 @@ fun Application.configureMongo() {
val memberService = runCatching { val memberService = runCatching {
MemberService(mongoDatabase) MemberService(mongoDatabase)
}.getOrNull() ?: return }.getOrNull() ?: return
val battleRecordService = runCatching {
BattleRecordService(mongoDatabase)
}.getOrNull() ?: return
routing { routing {
route("/api") { route("/api") {
// Read member // Read member
get("/member") { get("/member") {
memberService.read().let { member -> memberService.read().let { member ->
call.respond(member) call.respond(HttpStatusCode.OK, member)
} }
} }
// Read member // Read member
get("/member/{uid}") { get("/member/{uid}") {
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found") val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
memberService.read(uid)?.let { member -> memberService.read(uid)?.let { member ->
call.respond(member) call.respond(HttpStatusCode.OK, member)
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)
} }
// Create member // Create member
@@ -56,6 +63,45 @@ fun Application.configureMongo() {
} }
// TODO Battle Record // TODO Battle Record
get("/battle_record/{date}") {
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
battleRecordService.read(date).let { battleRecord ->
call.respond(HttpStatusCode.OK, battleRecord)
}
}
post("/battle_record") {
val request = call.receive<BattleRecordRequest>()
val newAttackDetail = AttackDetail(
bossRound = request.round,
bossIndex = request.boss,
damage = request.damage,
isCompensation = request.isCompensation,
)
val existData = battleRecordService.read(request.date, request.uid)
if (existData != null) {
val attackDetails = existData.attackDetailList.toMutableList()
attackDetails.add(newAttackDetail.toDto())
val newData = existData.copy(
attackDetailList = attackDetails
)
battleRecordService.update(request.date, newData)?.let {
call.respond(HttpStatusCode.OK, it)
} ?: call.respond(HttpStatusCode.NotFound)
} else {
val newData = BattleRecordDto(
date = request.date,
uid = request.uid,
playerName = request.name,
discordName = request.discordName,
discordID = request.discordID,
attackDetailList = listOf(newAttackDetail.toDto())
)
val id = battleRecordService.create(newData)
call.respond(HttpStatusCode.Created, id)
}
}
} }
} }