From 8c99ba1147c9e53cca21a211d071ce3de51b2ea9 Mon Sep 17 00:00:00 2001 From: Raymond Yang Date: Tue, 4 Aug 2026 21:11:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AF=A6=E4=BD=9C=E5=87=BA?= =?UTF-8?q?=E5=88=80=E7=B4=80=E9=8C=84=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/dto/BattleRecordDto.kt | 43 ++++++++++++++ .../database/dto/MemberDto.kt | 12 ++-- .../database/service/BattleRecordService.kt | 58 +++++++++++++++++++ .../database/service/MemberService.kt | 10 ++-- .../mapper/BattleRecordMapper.kt | 24 ++++++++ .../mapper/MemberMapper.kt | 2 +- .../model/BattleRecord.kt | 21 +++++++ .../model/BattleRecordRequest.kt | 17 ++++++ .../pcrediveclanrecordbackend/model/Member.kt | 2 +- .../plugins/Mongo.kt | 50 +++++++++++++++- 10 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/BattleRecordDto.kt create mode 100644 src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/BattleRecordService.kt create mode 100644 src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/BattleRecordMapper.kt create mode 100644 src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecord.kt create mode 100644 src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecordRequest.kt diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/BattleRecordDto.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/BattleRecordDto.kt new file mode 100644 index 0000000..9b7b86b --- /dev/null +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/BattleRecordDto.kt @@ -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 +) { + 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()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/MemberDto.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/MemberDto.kt index aba9973..212db94 100644 --- a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/MemberDto.kt +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/dto/MemberDto.kt @@ -1,18 +1,20 @@ 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 MemberDto( var uid: String, - val playerName: String, - val nickName: String, - val discordID: String, + @SerialName("player_name") val playerName: String, + @SerialName("discord_name") val discordName: String, + @SerialName("discord_id") val discordID: String, var leave: Boolean = false, - var createAt: Long? = null, - var updatedAt: Long? = null + @SerialName("created_at") var createAt: Long? = null, + @SerialName("updated_at") var updatedAt: Long? = null ) { fun toDocument(): Document = Document.parse(Json.encodeToString(this)) diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/BattleRecordService.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/BattleRecordService.kt new file mode 100644 index 0000000..ec6f818 --- /dev/null +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/BattleRecordService.kt @@ -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 + + 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 = 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) + } +} diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/MemberService.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/MemberService.kt index 7a364eb..d66d454 100644 --- a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/MemberService.kt +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/database/service/MemberService.kt @@ -16,7 +16,7 @@ class MemberService(private val database: MongoDatabase) { collection = database.getCollection("member") } - // Create new car + // Create new member suspend fun create(data: MemberDto): String = withContext(Dispatchers.IO) { data.createAt = System.currentTimeMillis() data.updatedAt = System.currentTimeMillis() @@ -26,22 +26,22 @@ class MemberService(private val database: MongoDatabase) { doc["uid"].toString() } - // Read a car + // Read a member suspend fun read(): List = withContext(Dispatchers.IO) { collection.find().toList().map(MemberDto::fromDocument) } - // Read a car + // Read a member suspend fun read(uid: String): MemberDto? = withContext(Dispatchers.IO) { 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) { collection.findOneAndReplace(Filters.eq("uid", uid), data.toDocument()) } - // Delete a car + // Delete a member suspend fun delete(uid: String): Document? = withContext(Dispatchers.IO) { collection.findOneAndDelete(Filters.eq("uid", uid)) } diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/BattleRecordMapper.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/BattleRecordMapper.kt new file mode 100644 index 0000000..3c8ae8e --- /dev/null +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/BattleRecordMapper.kt @@ -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, +) \ No newline at end of file diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/MemberMapper.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/MemberMapper.kt index e1becfd..fc44e99 100644 --- a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/MemberMapper.kt +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/mapper/MemberMapper.kt @@ -7,7 +7,7 @@ fun Member.toDto(): MemberDto { return MemberDto( uid = uid, playerName = playerName, - nickName = nickName, + discordName = discordName, discordID = discordID, leave = leave, createAt = createAt, diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecord.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecord.kt new file mode 100644 index 0000000..d9ade6e --- /dev/null +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecord.kt @@ -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 +) + +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() +) \ No newline at end of file diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecordRequest.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecordRequest.kt new file mode 100644 index 0000000..83be73c --- /dev/null +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/BattleRecordRequest.kt @@ -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, +) diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/Member.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/Member.kt index b54664b..9f156dc 100644 --- a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/Member.kt +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/model/Member.kt @@ -3,7 +3,7 @@ package club.sakunadaisuki.pcrediveclanrecordbackend.model data class Member( var uid: String, val playerName: String, - val nickName: String, + val discordName: String, val discordID: String, var leave: Boolean = false, var createAt: Long? = null, diff --git a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/plugins/Mongo.kt b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/plugins/Mongo.kt index bfbdd33..c9fa25e 100644 --- a/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/plugins/Mongo.kt +++ b/src/main/kotlin/club/sakunadaisuki/pcrediveclanrecordbackend/plugins/Mongo.kt @@ -1,8 +1,12 @@ 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.database.service.MemberService import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toDto +import club.sakunadaisuki.pcrediveclanrecordbackend.model.AttackDetail +import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecordRequest import com.mongodb.client.* import io.ktor.http.* import io.ktor.server.application.* @@ -17,20 +21,23 @@ fun Application.configureMongo() { val memberService = runCatching { MemberService(mongoDatabase) }.getOrNull() ?: return + val battleRecordService = runCatching { + BattleRecordService(mongoDatabase) + }.getOrNull() ?: return routing { route("/api") { // Read member get("/member") { memberService.read().let { member -> - call.respond(member) + call.respond(HttpStatusCode.OK, member) } } // Read member get("/member/{uid}") { val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found") memberService.read(uid)?.let { member -> - call.respond(member) + call.respond(HttpStatusCode.OK, member) } ?: call.respond(HttpStatusCode.NotFound) } // Create member @@ -56,6 +63,45 @@ fun Application.configureMongo() { } // 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() + 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) + } + } + } }