Compare commits
14 Commits
44601131a2
...
Develope
| Author | SHA1 | Date | |
|---|---|---|---|
| f4c4c6906e | |||
| 0cf0f8c49b | |||
| f8a1328a03 | |||
| f677fdb162 | |||
| abd4c24c83 | |||
| 7c390aebdf | |||
| 7f0d0f4a87 | |||
| 8b20758404 | |||
| 0741d5846b | |||
| 790e55f6fa | |||
| 5dfaa68e0e | |||
| e936250391 | |||
| 017b7edc24 | |||
| 4e32adbe44 |
@@ -3,6 +3,10 @@ FROM gradle:9.5.1-jdk25 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
ENV GRADLE_OPTS="-Xmx1536m -XX:MaxMetaspaceSize=512m"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=1536"
|
||||
|
||||
RUN gradle clean shadowJar --no-daemon
|
||||
|
||||
# 運行階段
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ data class AttackDetailDto(
|
||||
@SerialName("boss_index") val bossIndex: Int, // 1~5 號王
|
||||
val damage: Long, // 傷害
|
||||
@SerialName("is_compensation") val isCompensation: Boolean = false, // true: 補償刀, false: 正刀
|
||||
@SerialName("is_dropped") val isDropped: Boolean = false, // true: 掉刀, false: 沒掉刀
|
||||
@SerialName("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
|
||||
@SerialName("updated_at") val updatedAt: Long = Instant.now().toEpochMilli()
|
||||
) {
|
||||
|
||||
+2
-2
@@ -8,11 +8,11 @@ import java.time.Instant
|
||||
|
||||
@Serializable
|
||||
data class MemberDto(
|
||||
var uid: String,
|
||||
@SerialName("uid") var uid: String,
|
||||
@SerialName("player_name") val playerName: String,
|
||||
@SerialName("discord_name") val discordName: String,
|
||||
@SerialName("discord_id") val discordID: String,
|
||||
var leave: Boolean = false,
|
||||
@SerialName("leave") var leave: Boolean,
|
||||
@SerialName("created_at") var createAt: Long? = null,
|
||||
@SerialName("updated_at") var updatedAt: Long? = null
|
||||
) {
|
||||
|
||||
+46
-5
@@ -1,6 +1,9 @@
|
||||
package club.sakunadaisuki.pcrediveclanrecordbackend.database.service
|
||||
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toEmptyBattleRecordDto
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecord
|
||||
import com.mongodb.client.MongoCollection
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import com.mongodb.client.model.Filters
|
||||
@@ -8,28 +11,34 @@ import com.mongodb.client.model.Filters.and
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.bson.Document
|
||||
import java.time.LocalDate
|
||||
import java.time.YearMonth
|
||||
|
||||
class BattleRecordService(private val database: MongoDatabase) {
|
||||
var collection: MongoCollection<Document>
|
||||
var memberCollection: MongoCollection<Document>
|
||||
|
||||
init {
|
||||
database.createCollection("battle_records")
|
||||
collection = database.getCollection("battle_records")
|
||||
|
||||
database.createCollection("member")
|
||||
memberCollection = database.getCollection("member")
|
||||
}
|
||||
|
||||
// Create new car
|
||||
// Create new battle record
|
||||
suspend fun create(data: BattleRecordDto): BattleRecordDto = withContext(Dispatchers.IO) {
|
||||
val doc = data.toDocument()
|
||||
collection.insertOne(doc)
|
||||
doc.let(BattleRecordDto::fromDocument)
|
||||
}
|
||||
|
||||
// Read a car
|
||||
// Read a battle record
|
||||
suspend fun read(date: String): List<BattleRecordDto> = withContext(Dispatchers.IO) {
|
||||
collection.find(Filters.eq("date", date)).toList().map(BattleRecordDto::fromDocument)
|
||||
}
|
||||
|
||||
// Read a car
|
||||
// Read a battle record
|
||||
suspend fun read(date: String, uid: String): BattleRecordDto? = withContext(Dispatchers.IO) {
|
||||
val filter = and(
|
||||
Filters.eq("date", date),
|
||||
@@ -38,7 +47,7 @@ class BattleRecordService(private val database: MongoDatabase) {
|
||||
collection.find(filter).first()?.let(BattleRecordDto::fromDocument)
|
||||
}
|
||||
|
||||
// Update a car
|
||||
// Update a battle record
|
||||
suspend fun update(date: String, data: BattleRecordDto): BattleRecordDto? = withContext(Dispatchers.IO) {
|
||||
val filter = and(
|
||||
Filters.eq("date", date),
|
||||
@@ -48,7 +57,7 @@ class BattleRecordService(private val database: MongoDatabase) {
|
||||
collection.find(filter).first()?.let(BattleRecordDto::fromDocument)
|
||||
}
|
||||
|
||||
// Delete a car
|
||||
// Delete a battle record
|
||||
suspend fun delete(date: String, uid: String): Document? = withContext(Dispatchers.IO) {
|
||||
val filter = and(
|
||||
Filters.eq("date", date),
|
||||
@@ -56,4 +65,36 @@ class BattleRecordService(private val database: MongoDatabase) {
|
||||
)
|
||||
collection.findOneAndDelete(filter)
|
||||
}
|
||||
|
||||
suspend fun createTable(date: String): List<BattleRecordDto> = withContext(Dispatchers.IO) {
|
||||
val memberList = memberCollection.find(Filters.eq("leave", false)).toList().map(MemberDto::fromDocument)
|
||||
val emptyBattleRecordList = memberList.map { it.toEmptyBattleRecordDto(date) }
|
||||
collection.insertMany(emptyBattleRecordList.map { it.toDocument() })
|
||||
collection.find(Filters.eq("date", date)).toList().map(BattleRecordDto::fromDocument)
|
||||
}
|
||||
|
||||
suspend fun createTableMonth(date: String): List<List<BattleRecordDto>> = withContext(Dispatchers.IO) {
|
||||
val memberList = memberCollection.find(Filters.eq("leave", false)).toList().map(MemberDto::fromDocument)
|
||||
|
||||
// 1. 指定年份與月份(例如:2026年8月)
|
||||
val dateSplit = date.split("-").map { it.toInt() }
|
||||
val yearMonth = YearMonth.of(dateSplit[0], dateSplit[1])
|
||||
|
||||
// 2. 取得該月最後一天的 LocalDate 物件
|
||||
val lastDayOfMonth = yearMonth.atEndOfMonth()
|
||||
|
||||
// 3. 取得最後 5 天的日期列表(由舊到新排序)
|
||||
val lastFiveDays: List<LocalDate> = (4 downTo 0).map { lastDayOfMonth.minusDays(it.toLong()) }
|
||||
|
||||
// 4. Temp list
|
||||
val tables = mutableListOf<List<BattleRecordDto>>()
|
||||
|
||||
// 5. 寫入資料
|
||||
lastFiveDays.forEach { date ->
|
||||
val emptyBattleRecordList = memberList.map { it.toEmptyBattleRecordDto(date.toString()) }
|
||||
tables.add(emptyBattleRecordList)
|
||||
collection.insertMany(emptyBattleRecordList.map { it.toDocument() })
|
||||
}
|
||||
tables
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -17,13 +17,13 @@ class MemberService(private val database: MongoDatabase) {
|
||||
}
|
||||
|
||||
// Create new member
|
||||
suspend fun create(data: MemberDto): String = withContext(Dispatchers.IO) {
|
||||
suspend fun create(data: MemberDto): MemberDto = withContext(Dispatchers.IO) {
|
||||
data.createAt = System.currentTimeMillis()
|
||||
data.updatedAt = System.currentTimeMillis()
|
||||
data.leave = false
|
||||
val doc = data.toDocument()
|
||||
collection.insertOne(doc)
|
||||
doc["uid"].toString()
|
||||
doc.let(MemberDto::fromDocument)
|
||||
}
|
||||
|
||||
// Read a member
|
||||
@@ -37,8 +37,17 @@ class MemberService(private val database: MongoDatabase) {
|
||||
}
|
||||
|
||||
// Update a member
|
||||
suspend fun update(uid: String, data: MemberDto): Document? = withContext(Dispatchers.IO) {
|
||||
collection.findOneAndReplace(Filters.eq("uid", uid), data.toDocument())
|
||||
suspend fun update(uid: String, data: MemberDto): MemberDto? = withContext(Dispatchers.IO) {
|
||||
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)?.copy(
|
||||
playerName = data.playerName,
|
||||
discordName = data.discordName,
|
||||
discordID = data.discordID,
|
||||
leave = data.leave,
|
||||
updatedAt = System.currentTimeMillis(),
|
||||
)?.let {
|
||||
collection.findOneAndReplace(Filters.eq("uid", uid), it.toDocument())
|
||||
}
|
||||
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)
|
||||
}
|
||||
|
||||
// Delete a member
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ fun AttackDetail.toDto(): AttackDetailDto = AttackDetailDto(
|
||||
bossIndex = bossIndex,
|
||||
damage = damage,
|
||||
isCompensation = isCompensation,
|
||||
isDropped = isDropped,
|
||||
createdAt = createdAt,
|
||||
updatedAt = updatedAt,
|
||||
)
|
||||
@@ -1,8 +1,10 @@
|
||||
package club.sakunadaisuki.pcrediveclanrecordbackend.mapper
|
||||
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.Member
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.UpdateMemberRequest
|
||||
|
||||
fun Member.toDto(): MemberDto {
|
||||
return MemberDto(
|
||||
@@ -25,3 +27,24 @@ fun MemberRequest.toDto(): MemberDto {
|
||||
leave = leave,
|
||||
)
|
||||
}
|
||||
|
||||
fun UpdateMemberRequest.toDto(uid: String): MemberDto {
|
||||
return MemberDto(
|
||||
uid = uid,
|
||||
playerName = playerName,
|
||||
discordName = discordName,
|
||||
discordID = discordID,
|
||||
leave = leave,
|
||||
)
|
||||
}
|
||||
|
||||
fun MemberDto.toEmptyBattleRecordDto(date: String): BattleRecordDto {
|
||||
return BattleRecordDto(
|
||||
date = date,
|
||||
uid = uid,
|
||||
playerName = playerName,
|
||||
discordName = discordName,
|
||||
discordID = discordID,
|
||||
attackDetailList = emptyList(),
|
||||
)
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.AttackDetailDto
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@Serializable
|
||||
data class BattleRecord(
|
||||
val date: String, // yyyy-MM-dd
|
||||
var uid: String,
|
||||
@SerialName("date") val date: String, // yyyy-MM-dd
|
||||
@SerialName("uid") 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<AttackDetail>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AttackDetail(
|
||||
@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("damage") val damage: Long, // 傷害
|
||||
@SerialName("is_compensation") val isCompensation: Boolean, // true: 補償刀, false: 正刀
|
||||
@SerialName("is_dropped") val isDropped: Boolean, // true: 掉刀, false: 沒掉刀
|
||||
@SerialName("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
|
||||
@SerialName("updated_at") val updatedAt: Long = Instant.now().toEpochMilli()
|
||||
)
|
||||
+1
@@ -14,4 +14,5 @@ data class BattleRecordRequest(
|
||||
val boss: Int,
|
||||
val damage: Long,
|
||||
@SerialName("is_compensation") val isCompensation: Boolean = false,
|
||||
@SerialName("is_dropped") val isDropped: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Member(
|
||||
var uid: String,
|
||||
@SerialName("uid") var uid: String,
|
||||
@SerialName("player_name") val playerName: String,
|
||||
@SerialName("discord_name") val discordName: String,
|
||||
@SerialName("discord_id") val discordID: String,
|
||||
var leave: Boolean = false,
|
||||
@SerialName("leave") var leave: Boolean,
|
||||
@SerialName("created_at") var createAt: Long? = null,
|
||||
@SerialName("updated_at") var updatedAt: Long? = null
|
||||
)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UpdateMemberRequest(
|
||||
@SerialName("player_name") val playerName: String,
|
||||
@SerialName("discord_name") val discordName: String,
|
||||
@SerialName("discord_id") val discordID: String,
|
||||
@SerialName("leave") var leave: Boolean,
|
||||
)
|
||||
@@ -8,13 +8,21 @@ import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureHttp() {
|
||||
install(CORS) {
|
||||
allowHost("localhost:8080")
|
||||
allowHost("table.sakunadaisuki.club")
|
||||
allowHost("192.168.50.128:10002")
|
||||
|
||||
allowMethod(HttpMethod.Options)
|
||||
allowMethod(HttpMethod.Put)
|
||||
allowMethod(HttpMethod.Delete)
|
||||
allowMethod(HttpMethod.Patch)
|
||||
allowHeader(HttpHeaders.Authorization)
|
||||
|
||||
allowHeader(HttpHeaders.ContentType) // 處理 JSON 必備
|
||||
allowHeader(HttpHeaders.Authorization) // 傳遞 JWT / 身份驗證權限必備
|
||||
|
||||
allowHeader("MyCustomHeader")
|
||||
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
||||
//anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
||||
}
|
||||
routing {
|
||||
swaggerUI(path = "openapi") {
|
||||
|
||||
@@ -2,16 +2,18 @@ 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.BattleRecord
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecordRequest
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
|
||||
import com.mongodb.client.*
|
||||
import club.sakunadaisuki.pcrediveclanrecordbackend.model.UpdateMemberRequest
|
||||
import com.mongodb.client.MongoClients
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.config.tryGetString
|
||||
import io.ktor.server.config.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
@@ -34,6 +36,7 @@ fun Application.configureMongo() {
|
||||
call.respond(HttpStatusCode.OK, member)
|
||||
}
|
||||
}
|
||||
|
||||
// Read member
|
||||
get("/member/{uid}") {
|
||||
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||
@@ -41,20 +44,28 @@ fun Application.configureMongo() {
|
||||
call.respond(HttpStatusCode.OK, member)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
// Create member
|
||||
post("/member") {
|
||||
val member = call.receive<MemberRequest>()
|
||||
val id = memberService.create(member.toDto())
|
||||
call.respond(HttpStatusCode.Created, id)
|
||||
val existMember = memberService.read(member.uid)
|
||||
if (existMember == null) {
|
||||
val id = memberService.create(member.toDto())
|
||||
call.respond(HttpStatusCode.Created, id)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.Conflict)
|
||||
}
|
||||
}
|
||||
|
||||
// Update member
|
||||
put("/member/{uid}") {
|
||||
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||
val member = call.receive<Member>()
|
||||
memberService.update(uid, member.toDto())?.let {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
val member = call.receive<UpdateMemberRequest>()
|
||||
memberService.update(uid, member.toDto(uid))?.let { member ->
|
||||
call.respond(HttpStatusCode.OK, member)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
// Delete member
|
||||
delete("/member/{uid}") {
|
||||
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||
@@ -71,6 +82,21 @@ fun Application.configureMongo() {
|
||||
}
|
||||
}
|
||||
|
||||
get("/battle_record/{date}/{uid}") {
|
||||
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||
battleRecordService.read(date, uid)?.let { battleRecord ->
|
||||
call.respond(HttpStatusCode.OK, battleRecord)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
put("/battle_record") {
|
||||
val request = call.receive<BattleRecord>()
|
||||
battleRecordService.update(request.date, request.toDto())?.let { battleRecord ->
|
||||
call.respond(HttpStatusCode.OK, battleRecord)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
post("/battle_record") {
|
||||
val request = call.receive<BattleRecordRequest>()
|
||||
val newAttackDetail = AttackDetail(
|
||||
@@ -78,6 +104,7 @@ fun Application.configureMongo() {
|
||||
bossIndex = request.boss,
|
||||
damage = request.damage,
|
||||
isCompensation = request.isCompensation,
|
||||
isDropped = request.isDropped,
|
||||
)
|
||||
val existData = battleRecordService.read(request.date, request.uid)
|
||||
if (existData != null) {
|
||||
@@ -103,7 +130,24 @@ fun Application.configureMongo() {
|
||||
}
|
||||
}
|
||||
|
||||
post("/battle_record/create_table_day/{date}") {
|
||||
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||
val exist = battleRecordService.read(date)
|
||||
if (exist.isNotEmpty()) {
|
||||
call.respond(HttpStatusCode.Conflict)
|
||||
return@post
|
||||
}
|
||||
battleRecordService.createTable(date).let { battleRecord ->
|
||||
call.respond(HttpStatusCode.OK, battleRecord)
|
||||
}
|
||||
}
|
||||
|
||||
post("/battle_record/create_table_month/{date}") {
|
||||
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||
battleRecordService.createTableMonth(date).let { battleRecordList ->
|
||||
call.respond(HttpStatusCode.OK, battleRecordList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user