Compare commits

3 Commits

Author SHA1 Message Date
ray650128 77a1690e08 調整各層資料轉換 2026-08-04 21:24:24 +08:00
ray650128 8c99ba1147 初步實作出刀紀錄表 2026-08-04 21:11:59 +08:00
Raymond Yang fcb5285788 重構成員管理部分 2026-08-04 16:36:38 +08:00
43 changed files with 714 additions and 727 deletions
+3 -3
View File
@@ -1,14 +1,14 @@
# 使用 Kotlin + JDK 的輕量化映像 # 使用 Kotlin + JDK 的輕量化映像
FROM gradle:7.5.1-jdk17-alpine AS builder FROM gradle:9-jdk25-alpine-dev AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN gradle clean shadowJar --no-daemon RUN gradle clean shadowJar --no-daemon
# 運行階段 # 運行階段
FROM eclipse-temurin:21-jdk-ubi10-minimal FROM eclipse-temurin:25-jdk-ubi10-minimal
WORKDIR /app WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar COPY --from=builder /app/build/libs/*.jar app.jar
CMD ["java", "-jar", "app.jar", "com.ray650128.pcredive.ApplicationKt"] CMD ["java", "-jar", "app.jar", "club.sakunadaisuki.pcrediveclanrecordbackend.mainKt"]
+38
View File
@@ -0,0 +1,38 @@
# PcReDiveClanRecordBackend2
This project was created using the [Ktor Project Generator](https://start.ktor.io).
Here are some useful links to get you started:
* [Ktor Documentation](https://ktor.io/docs/home.html)
* [Ktor GitHub page](https://github.com/ktorio/ktor)
* [Ktor Slack chat](https://app.slack.com/client/T09229ZC6/C0A974TJ9). [Request an invite](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up).
## Features
Here's a list of features included in this project:
| Name | Description |
|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| [CORS](https://start.ktor.io/p/io.ktor/server-cors) | Enables Cross-Origin Resource Sharing (CORS) |
| [Swagger](https://start.ktor.io/p/io.ktor/server-swagger) | Serves Swagger UI for your project |
| [Content Negotiation](https://start.ktor.io/p/io.ktor/server-content-negotiation) | Provides automatic content conversion according to Content-Type and Accept headers |
| [kotlinx.serialization](https://start.ktor.io/p/io.ktor/server-kotlinx-serialization) | Handles JSON serialization using kotlinx.serialization library |
| [MongoDB](https://start.ktor.io/p/org.jetbrains/server-mongodb) | Adds MongoDB database support |
## Building & Running
To build or run the project, use one of the following tasks:
| Task | Description |
|-------------------|-------------------|
| `./gradlew test` | Run the tests |
| `./gradlew build` | Build the project |
| `./gradlew run` | Run the server |
If the server starts successfully, you'll see the following output:
```
2024-12-04 14:32:45.584 [main] INFO Application - Application started in 0.303 seconds.
2024-12-04 14:32:45.682 [main] INFO Application - Responding at http://0.0.0.0:8080
```
+23 -45
View File
@@ -1,55 +1,33 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project
val kmongo_version: String by project
plugins { plugins {
kotlin("jvm") version "1.8.22" alias(libs.plugins.kotlin.jvm)
id("io.ktor.plugin") version "2.3.1" alias(ktorLibs.plugins.ktor)
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.22" alias(libs.plugins.kotlin.serialization)
id("com.github.johnrengelman.shadow") version "7.1.2"
} }
group = "com.ray650128.pcredive" group = "club.sakunadaisuki"
version = "0.0.1" version = "1.0.0-SNAPSHOT"
application { application {
mainClass.set("io.ktor.server.netty.EngineMain") mainClass = "io.ktor.server.netty.EngineMain"
val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
} }
repositories { kotlin {
mavenCentral() jvmToolchain(21)
} }
dependencies { dependencies {
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version") implementation(ktorLibs.serialization.kotlinx.json)
implementation("io.ktor:ktor-server-core-jvm:$ktor_version") implementation(ktorLibs.server.config.yaml)
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version") implementation(ktorLibs.server.contentNegotiation)
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version") implementation(ktorLibs.server.core)
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") implementation(ktorLibs.server.cors)
implementation("ch.qos.logback:logback-classic:$logback_version") implementation(ktorLibs.server.netty)
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") implementation(ktorLibs.server.routingOpenapi)
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") implementation(ktorLibs.server.swagger)
implementation(libs.logback.classic)
implementation(libs.mongodb.bson)
implementation(libs.mongodb.driverCore)
implementation(libs.mongodb.driverSync)
implementation("org.litote.kmongo:kmongo:$kmongo_version") testImplementation(kotlin("test"))
implementation("org.litote.kmongo:kmongo-id-serialization:$kmongo_version") testImplementation(ktorLibs.server.testHost)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
tasks.jar {
manifest {
attributes["Main-Class"] = "com.ray650128.bot.MainKt"
}
}
tasks.build {
dependsOn(tasks.shadowJar)
} }
+1 -4
View File
@@ -1,5 +1,2 @@
ktor_version=2.3.1
kotlin_version=1.8.22
logback_version=1.2.11
kotlin.code.style=official kotlin.code.style=official
kmongo_version=4.11.0
+15
View File
@@ -0,0 +1,15 @@
[versions]
kotlin = "2.4.0"
logback = "1.5.35"
mongodb = "5.8.0"
[libraries]
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
mongodb-bson = { module = "org.mongodb:bson", version.ref = "mongodb" }
mongodb-driverCore = { module = "org.mongodb:mongodb-driver-core", version.ref = "mongodb" }
mongodb-driverSync = { module = "org.mongodb:mongodb-driver-sync", version.ref = "mongodb" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Binary file not shown.
+5 -1
View File
@@ -1,5 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+34 -20
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,10 +15,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
# Gradle start up script for POSIX generated by Gradle. # gradlew start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
@@ -27,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh Gradle # ksh gradlew
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -133,22 +132,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -165,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+28 -35
View File
@@ -13,19 +13,22 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem gradlew startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute gradlew
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if "%ERRORLEVEL%"=="0" goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+22 -1
View File
@@ -1 +1,22 @@
rootProject.name = "PcReDivePveRecordBackend" pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("ktorLibs").from("io.ktor:ktor-version-catalog:3.5.0")
}
}
rootProject.name = "PcReDiveClanRecordBackend"
@@ -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())
}
}
@@ -0,0 +1,26 @@
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,
@SerialName("player_name") val playerName: String,
@SerialName("discord_name") val discordName: String,
@SerialName("discord_id") val discordID: String,
var leave: Boolean = false,
@SerialName("created_at") var createAt: Long? = null,
@SerialName("updated_at") var updatedAt: Long? = null
) {
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
companion object {
private val json = Json { ignoreUnknownKeys = true }
fun fromDocument(document: Document): MemberDto = json.decodeFromString(document.toJson())
}
}
@@ -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)
}
}
@@ -0,0 +1,48 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.database.service
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Filters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.bson.Document
class MemberService(private val database: MongoDatabase) {
var collection: MongoCollection<Document>
init {
database.createCollection("member")
collection = database.getCollection("member")
}
// Create new member
suspend fun create(data: MemberDto): String = withContext(Dispatchers.IO) {
data.createAt = System.currentTimeMillis()
data.updatedAt = System.currentTimeMillis()
data.leave = false
val doc = data.toDocument()
collection.insertOne(doc)
doc["uid"].toString()
}
// Read a member
suspend fun read(): List<MemberDto> = withContext(Dispatchers.IO) {
collection.find().toList().map(MemberDto::fromDocument)
}
// Read a member
suspend fun read(uid: String): MemberDto? = withContext(Dispatchers.IO) {
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)
}
// Update a member
suspend fun update(uid: String, data: MemberDto): Document? = withContext(Dispatchers.IO) {
collection.findOneAndReplace(Filters.eq("uid", uid), data.toDocument())
}
// Delete a member
suspend fun delete(uid: String): Document? = withContext(Dispatchers.IO) {
collection.findOneAndDelete(Filters.eq("uid", uid))
}
}
@@ -0,0 +1,7 @@
package club.sakunadaisuki.pcrediveclanrecordbackend
import io.ktor.server.netty.EngineMain
fun main(args: Array<String>) {
EngineMain.main(args)
}
@@ -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,
)
@@ -0,0 +1,27 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.mapper
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
import club.sakunadaisuki.pcrediveclanrecordbackend.model.Member
import club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
fun Member.toDto(): MemberDto {
return MemberDto(
uid = uid,
playerName = playerName,
discordName = discordName,
discordID = discordID,
leave = leave,
createAt = createAt,
updatedAt = updatedAt,
)
}
fun MemberRequest.toDto(): MemberDto {
return MemberDto(
uid = uid,
playerName = playerName,
discordName = discordName,
discordID = discordID,
leave = leave,
)
}
@@ -0,0 +1,23 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.model
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.AttackDetailDto
import kotlinx.serialization.SerialName
import java.time.Instant
data class BattleRecord(
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<AttackDetail>
)
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("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
@SerialName("updated_at") 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,
)
@@ -0,0 +1,15 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Member(
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("created_at") var createAt: Long? = null,
@SerialName("updated_at") var updatedAt: Long? = null
)
@@ -0,0 +1,13 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class MemberRequest(
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,
)
@@ -0,0 +1,30 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
import io.ktor.server.application.*
import io.ktor.http.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.swagger.*
import io.ktor.server.routing.*
fun Application.configureHttp() {
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHeader("MyCustomHeader")
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
}
routing {
swaggerUI(path = "openapi") {
/*
Documentation source configuration goes here.
This can be from file (documentation.yaml), or it can be served dynamically from your sources using the
`describe {}` API on routes. When `openApi` enabled in Gradle, these calls will be automatically injected
based on your code and comments.
*/
}
}
}
@@ -0,0 +1,148 @@
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 club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
import com.mongodb.client.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.config.tryGetString
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureMongo() {
// Connect to your mongo instance
val mongoDatabase = connectToMongoDB()
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(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(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)
}
// 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)
} ?: call.respond(HttpStatusCode.NotFound)
}
// Delete member
delete("/member/{uid}") {
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
memberService.delete(uid)?.let {
call.respond(HttpStatusCode.OK)
} ?: call.respond(HttpStatusCode.NotFound)
}
// 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)
}
}
}
}
}
/**
* Establishes connection with a MongoDB database.
*
* The following configuration properties (in application.yaml/application.conf) can be specified:
* * `db.mongo.user` username for your database
* * `db.mongo.password` password for the user
* * `db.mongo.host` host that will be used for the database connection
* * `db.mongo.port` port that will be used for the database connection
* * `db.mongo.maxPoolSize` maximum number of connections to a MongoDB server
* * `db.mongo.database.name` name of the database
*
* IMPORTANT NOTE: in order to make MongoDB connection working, you have to start a MongoDB server first.
* See the instructions here: https://www.mongodb.com/docs/manual/administration/install-community/
* all the paramaters above
*
* @returns [MongoDatabase] instance
* */
fun Application.connectToMongoDB(): MongoDatabase {
val user = environment.config.tryGetString("db.mongo.user")
val password = environment.config.tryGetString("db.mongo.password")
val host = environment.config.tryGetString("db.mongo.host") ?: "127.0.0.1"
val port = environment.config.tryGetString("db.mongo.port") ?: "27017"
val maxPoolSize = environment.config.tryGetString("db.mongo.maxPoolSize")?.toInt() ?: 20
val databaseName = environment.config.tryGetString("db.mongo.database.name") ?: "pc_re_dive_clan_battle"
val credentials = user?.let { userVal -> password?.let { passwordVal -> "$userVal:$passwordVal@" } }.orEmpty()
val uri = "mongodb://$credentials$host:$port/?maxPoolSize=$maxPoolSize&w=majority"
val mongoClient = MongoClients.create(uri)
val database = mongoClient.getDatabase(databaseName)
monitor.subscribe(ApplicationStopped) {
mongoClient.close()
}
return database
}
@@ -0,0 +1,16 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello, World!")
}
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
}
}
@@ -0,0 +1,11 @@
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
import io.ktor.server.application.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
}
@@ -1,23 +0,0 @@
package com.ray650128.pcredive
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.LoggerContext
import io.ktor.server.application.*
import com.ray650128.pcredive.plugins.*
import org.slf4j.LoggerFactory
fun main(args: Array<String>) {
val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
val rootLogger = loggerContext.getLogger("org.mongodb.driver")
rootLogger.level = Level.OFF
io.ktor.server.netty.EngineMain.main(args)
}
@Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused.
fun Application.module() {
configureUpdateTimer()
configureHTTP()
configureSerialization()
configureMemberRouting()
configureMemberRecordRouting()
}
@@ -1,40 +0,0 @@
package com.ray650128.pcredive.database.dto
import com.ray650128.pcredive.database.model.Member
import kotlinx.serialization.Serializable
@Serializable
data class MemberDto(
var _id: String? = null,
var playerName: String,
var nickName: String,
var discordID: String,
var uid: String,
var leave: Boolean = false,
var createAt: Long? = null,
var updatedAt: Long? = null
)
fun Member.toDto(): MemberDto {
return MemberDto(
_id = this._id.toString(),
playerName = this.playerName,
nickName = this.nickName,
discordID = this.discordID,
uid = this.uid,
leave = this.leave,
createAt = this.createAt,
updatedAt = this.updatedAt
)
}
fun MemberDto.toMember(): Member =
Member(
playerName = this.playerName,
nickName = this.nickName,
discordID = this.discordID,
uid = this.uid,
leave = this.leave,
createAt = this.createAt,
updatedAt = this.updatedAt
)
@@ -1,49 +0,0 @@
package com.ray650128.pcredive.database.dto
import com.ray650128.pcredive.database.model.Member
import com.ray650128.pcredive.database.model.MemberRecord
import com.ray650128.pcredive.database.model.Record
import com.ray650128.pcredive.database.service.MemberService
import kotlinx.serialization.Serializable
@Serializable
data class MemberRecordDto(
var _id: String? = null,
var member: Member? = null,
var record1: Record,
var record1c: Record,
var record2: Record,
var record2c: Record,
var record3: Record,
var record3c: Record,
var createdAt: Long?,
var updatedAt: Long?
)
fun MemberRecord.toDto(): MemberRecordDto {
return MemberRecordDto(
_id = this._id.toString(),
member = MemberService.findById(this.memberId.toString()),
record1 = this.record1,
record1c = this.record1c,
record2 = this.record2,
record2c = this.record2c,
record3 = this.record3,
record3c = this.record3c,
createdAt = this.createdAt,
updatedAt = this.updatedAt
)
}
fun MemberRecordDto.toMemberRecord(): MemberRecord =
MemberRecord(
memberId = this.member?._id,
record1 = this.record1,
record1c = this.record1c,
record2 = this.record2,
record2c = this.record2c,
record3 = this.record3,
record3c = this.record3c,
createdAt = this.createdAt,
updatedAt = this.updatedAt
)
@@ -1,18 +0,0 @@
package com.ray650128.pcredive.database.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.litote.kmongo.Id
import org.litote.kmongo.newId
@Serializable
data class Member(
@Contextual var _id: Id<Member> = newId(),
var playerName: String,
var nickName: String,
var discordID: String,
var uid: String,
var leave: Boolean = false,
var createAt: Long? = null,
var updatedAt: Long? = null
)
@@ -1,26 +0,0 @@
package com.ray650128.pcredive.database.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.litote.kmongo.Id
import org.litote.kmongo.newId
@Serializable
data class MemberRecord(
@Contextual var _id: Id<MemberRecord> = newId(),
@Contextual var memberId: Id<Member>? = null,
var record1: Record = Record(),
var record1c: Record = Record(),
var record2: Record = Record(),
var record2c: Record = Record(),
var record3: Record = Record(),
var record3c: Record = Record(),
var createdAt: Long? = null,
var updatedAt: Long? = null
)
@Serializable
data class Record(
var boss: String? = null,
var damage: Int? = null
)
@@ -1,9 +0,0 @@
package com.ray650128.pcredive.database.model
import kotlinx.serialization.Serializable
@Serializable
data class RecordData(
val member: Member,
var record: MemberRecord?
)
@@ -1,16 +0,0 @@
package com.ray650128.pcredive.database.model
import kotlinx.serialization.Serializable
@Serializable
data class UpdateMemberRecord(
var memberId: String,
var record1: Record = Record("未出"),
var record1c: Record = Record("未出"),
var record2: Record = Record("未出"),
var record2c: Record = Record("未出"),
var record3: Record = Record("未出"),
var record3c: Record = Record("未出"),
var createdAt: Long? = null,
var updatedAt: Long? = null
)
@@ -1,58 +0,0 @@
package com.ray650128.pcredive.database.service
import com.ray650128.pcredive.database.model.Member
import com.ray650128.pcredive.database.model.MemberRecord
import org.bson.types.ObjectId
import org.litote.kmongo.*
import org.litote.kmongo.id.toId
object MemberRecordService {
private val client = KMongo.createClient("mongodb://ray650128:Zx0987062271!@192.168.50.128:27017")
private val database = client.getDatabase("pc_re_dive_clan_battle")
private val recordCollection = database.getCollection<MemberRecord>()
fun create(record: MemberRecord): Id<MemberRecord> {
recordCollection.insertOne(record)
return record._id
}
fun findById(id: String): MemberRecord? {
val bsonId: Id<MemberRecord> = ObjectId(id).toId()
return recordCollection.findOne(MemberRecord::_id eq bsonId)
}
fun findByMemberIdBetweenDate(id: String, start: Long, end: Long): MemberRecord? {
val bsonId: Id<Member> = ObjectId(id).toId()
return recordCollection.findOne(MemberRecord::memberId eq bsonId, MemberRecord::createdAt gte start, MemberRecord::createdAt lt end)
}
fun findByTimeBetween(start: Long, end: Long): List<MemberRecord> {
return recordCollection.find(MemberRecord::createdAt gte start, MemberRecord::createdAt lt end).toList()
}
fun findByOwnerId(id: String): List<MemberRecord> {
val bsonId: Id<Member> = ObjectId(id).toId()
return recordCollection.find(MemberRecord::memberId eq bsonId).toList()
}
fun updateById(id: String, request: MemberRecord): Boolean =
findById(id)?.let { record ->
val updateResult = recordCollection.replaceOne(
record.copy(
record1 = request.record1,
record1c = request.record1c,
record2 = request.record2,
record2c = request.record2c,
record3 = request.record3,
record3c = request.record3c,
updatedAt = System.currentTimeMillis()
)
)
updateResult.modifiedCount == 1L
} ?: false
fun deleteById(id: String): Boolean {
val deleteResult = recordCollection.deleteOneById(ObjectId(id))
return deleteResult.deletedCount == 1L
}
}
@@ -1,52 +0,0 @@
package com.ray650128.pcredive.database.service
import com.ray650128.pcredive.database.model.Member
import org.bson.types.ObjectId
import org.litote.kmongo.*
import org.litote.kmongo.id.toId
object MemberService {
private val client = KMongo.createClient("mongodb://ray650128:Zx0987062271!@192.168.50.128:27017")
private val database = client.getDatabase("pc_re_dive_clan_battle")
private val memberCollection = database.getCollection<Member>()
fun create(member: Member): Id<Member> {
memberCollection.insertOne(member)
return member._id
}
//fun findAll(): List<Member> = memberCollection.find().toList()
fun findAll(isLeave: Boolean? = null): List<Member> {
return if (isLeave == null) {
memberCollection.find().toList()
} else {
memberCollection.find(Member::leave eq isLeave).toList()
}
}
fun findById(id: String): Member? {
val bsonId: Id<Member> = ObjectId(id).toId()
return memberCollection.findOne(Member::_id eq bsonId)
}
fun updateById(id: String, request: Member): Boolean =
findById(id)?.let { member ->
val updateResult = memberCollection.replaceOne(
member.copy(
playerName = request.playerName,
nickName = request.nickName,
discordID = request.discordID,
uid = request.uid,
leave = request.leave,
updatedAt = request.updatedAt
)
)
updateResult.modifiedCount == 1L
} ?: false
fun deleteById(id: String): Boolean {
val deleteResult = memberCollection.deleteOneById(ObjectId(id))
return deleteResult.deletedCount == 1L
}
}
@@ -1,26 +0,0 @@
package com.ray650128.pcredive.plugins
import io.ktor.http.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.*
import java.io.File
fun Application.configureHTTP() {
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Post)
allowHeader(HttpHeaders.AccessControlAllowOrigin)
allowHeader(HttpHeaders.Accept)
allowHeader(HttpHeaders.ContentType)
anyHost()
allowCredentials = true
allowNonSimpleContentTypes = true
}
}
@@ -1,157 +0,0 @@
package com.ray650128.pcredive.plugins
import com.ray650128.pcredive.database.dto.toDto
import com.ray650128.pcredive.database.model.MemberRecord
import com.ray650128.pcredive.database.model.Record
import com.ray650128.pcredive.database.model.RecordData
import com.ray650128.pcredive.database.model.UpdateMemberRecord
import com.ray650128.pcredive.database.service.MemberRecordService
import com.ray650128.pcredive.database.service.MemberService
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import io.ktor.server.util.*
import kotlinx.coroutines.coroutineScope
import org.litote.kmongo.toId
import java.util.*
fun Application.configureMemberRecordRouting() {
routing {
options("{...}") {
call.respond(HttpStatusCode.OK)
}
route("/api") {
route("/record") { // 查詢所有紀錄
get {
val calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis() - (5 * 60 * 60 * 1000) // 由於公連換日為每日早上5:00,因此減去時差
}
val records = getData(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH))
call.respond(HttpStatusCode.OK, records)
}
post("/{id}") { // 新增成員紀錄
val id = call.parameters.getOrFail<String>("id")
val record = call.receive<MemberRecord>()
val member = MemberService.findById(id) ?: run {
call.respond(HttpStatusCode.NotFound)
return@post
}
val now = Calendar.getInstance()
val startTime = getDayStartLong(now[Calendar.YEAR], now[Calendar.MONTH], now[Calendar.DAY_OF_MONTH])
val endTime = getDayEndLong(now[Calendar.YEAR], now[Calendar.MONTH], now[Calendar.DAY_OF_MONTH])
val existData = MemberRecordService.findByMemberIdBetweenDate(id, startTime, endTime) ?: run {
record.apply {
memberId = member._id
createdAt = System.currentTimeMillis()
updatedAt = System.currentTimeMillis()
}
MemberRecordService.create(record).let {
call.respond(HttpStatusCode.OK, record)
}
return@post
}
existData.apply {
memberId = member._id
record1 = record.record1
record1c = record.record1c
record2 = record.record2
record2c = record.record2c
record3 = record.record3
record3c = record.record3c
updatedAt = System.currentTimeMillis()
}
MemberRecordService.updateById(existData._id.toString(), existData).let {
call.respond(HttpStatusCode.OK, existData)
}
}
get("/{year}/{month}/{day}") { // 取得特定日期
val year = call.parameters.getOrFail<Int>("year").toInt()
val month = call.parameters.getOrFail<Int>("month").toInt() - 1
val day = call.parameters.getOrFail<Int>("day").toInt()
val records = getData(year, month, day)
call.respond(HttpStatusCode.OK, records)
}
get("/{id}") {
// Show an article with a specific id
val id = call.parameters.getOrFail<String>("id")
val record = MemberRecordService.findById(id)?.toDto() ?: run {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(HttpStatusCode.OK, record)
}
put("/{id}") {
val id = call.parameters.getOrFail<String>("id")
val record = call.receive<UpdateMemberRecord>()
val oldData = MemberRecordService.findById(id) ?: run {
call.respond(HttpStatusCode.NotFound)
return@put
}
oldData.apply {
record1 = record.record1
record1c = record.record1c
record2 = record.record2
record2c = record.record2c
record3 = record.record3
record3c = record.record3c
updatedAt = System.currentTimeMillis()
}
MemberRecordService.updateById(id, oldData)
call.respond(HttpStatusCode.OK, record)
}
}
}
}
}
fun getDayStartLong(year: Int, month: Int, day: Int): Long {
val calendar = Calendar.getInstance().apply {
set(Calendar.YEAR, year)
set(Calendar.MONTH, month)
set(Calendar.DAY_OF_MONTH, day)
}
return calendar.apply {
set(Calendar.HOUR_OF_DAY, 5)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
fun getDayEndLong(year: Int, month: Int, day: Int): Long {
val startTime = getDayStartLong(year, month, day)
return startTime + 86399000L
}
fun getData(year: Int, month: Int, day: Int): List<RecordData> {
val startTime = getDayStartLong(year, month, day)
val endTime = getDayEndLong(year, month, day)
val records = ArrayList<RecordData>()
val memberList = MemberService.findAll()
memberList.forEach { member ->
if (member.leave && member.updatedAt!! < startTime) {
//println("成員離開 ${member.nickName}")
return@forEach
}
if (member.createAt!! > startTime) {
//println("成員不在當期開戰期間 ${member.nickName}")
return@forEach
}
records.add(
RecordData(member = member, null)
)
}
val recordList = MemberRecordService.findByTimeBetween(startTime, endTime)
recordList.forEach { record ->
val player = MemberService.findById(record.memberId.toString())
records.firstOrNull { it.member == player }?.let { playerRecord ->
playerRecord.record = record
}
}
return records
}
@@ -1,74 +0,0 @@
package com.ray650128.pcredive.plugins
import com.ray650128.pcredive.database.model.Member
import com.ray650128.pcredive.database.service.MemberService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.util.*
import kotlinx.coroutines.coroutineScope
fun Application.configureMemberRouting() {
routing {
options("{...}") {
call.respond(HttpStatusCode.OK)
}
route("/api") {
route("/member") {
get {
// 顯示所有登錄的成員資料
coroutineScope {
val memberList = MemberService.findAll()
call.respond(HttpStatusCode.OK, memberList)
}
}
get("/{id}") {
val id = call.parameters.getOrFail<String>("id")
val member = MemberService.findById(id) ?: run {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(HttpStatusCode.OK, member)
}
post {
val member = call.receive<Member>()
member.apply {
createAt = System.currentTimeMillis()
updatedAt = System.currentTimeMillis()
}
MemberService.create(member).let {
call.respond(HttpStatusCode.OK, member)
}
}
put("/{id}") {
val id = call.parameters.getOrFail<String>("id")
val member = call.receive<Member>()
val oldData = MemberService.findById(id) ?: run {
call.respond(HttpStatusCode.NotFound)
return@put
}
oldData.apply {
playerName = member.playerName
nickName = member.nickName
discordID = member.discordID
uid = member.uid
leave = member.leave
updatedAt = System.currentTimeMillis()
}
MemberService.updateById(id, oldData)
call.respond(HttpStatusCode.OK, oldData)
}
delete("/{id}") {
val id = call.parameters.getOrFail<String>("id")
if (MemberService.deleteById(id)) {
call.respond(HttpStatusCode.OK)
} else {
call.respond(HttpStatusCode.NotFound)
}
}
}
}
}
}
@@ -1,17 +0,0 @@
package com.ray650128.pcredive.plugins
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(
Json { serializersModule = IdKotlinXSerializationModule }
)
}
}
@@ -1,42 +0,0 @@
package com.ray650128.pcredive.plugins
import com.ray650128.pcredive.database.model.MemberRecord
import com.ray650128.pcredive.database.model.Record
import com.ray650128.pcredive.database.service.MemberRecordService
import com.ray650128.pcredive.database.service.MemberService
import io.ktor.server.application.*
import java.util.*
fun Application.configureUpdateTimer() {
val date = Date()
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
val c = Calendar.getInstance()
val hour = c[Calendar.HOUR_OF_DAY]
val minute = c[Calendar.MINUTE]
val second = c[Calendar.SECOND]
val day = c[Calendar.DAY_OF_MONTH]
val lastDay = c.getActualMaximum(Calendar.DAY_OF_MONTH)
//println("Time: $day, ${String.format("%2d:%2d:%2d", hour, minute, second)}")
if ((day in (lastDay - 5)..lastDay) && (hour == 5 && minute == 0 && second == 0)) {
val members = MemberService.findAll(false)
members.forEach { member ->
MemberRecordService.create(
MemberRecord(
memberId = member._id,
record1 = Record(""),
record1c = Record(""),
record2 = Record(""),
record2c = Record(""),
record3 = Record(""),
record3c = Record(""),
createdAt = System.currentTimeMillis(),
updatedAt = System.currentTimeMillis()
)
)
}
}
}
}, date, 1000L)
}
-9
View File
@@ -1,9 +0,0 @@
ktor {
deployment {
port = 10001
port = ${?PORT}
}
application {
modules = [ com.ray650128.pcredive.ApplicationKt.module ]
}
}
+14
View File
@@ -0,0 +1,14 @@
ktor:
deployment:
port: 8080
application:
modules:
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.HttpKt.configureHttp
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.SerializationKt.configureSerialization
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.MongoKt.configureMongo
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.RoutingKt.configureRouting
db:
mongo:
user: ray650128
password: Zx0987062271!
host: 192.168.50.128
+23
View File
@@ -0,0 +1,23 @@
openapi: "3.0.3"
info:
title: "Application API"
description: "Application API"
version: "1.0.0"
servers:
- url: "http://0.0.0.0:8080"
paths:
/:
get:
description: "Hello World!"
responses:
"200":
description: "OK"
content:
text/plain:
schema:
type: "string"
examples:
Example#1:
value: "Hello World!"
components:
schemas: { }
+1 -1
View File
@@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="trace"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>