From 50dbcf60e11791c33c41978ff46b8a622a1bcce9 Mon Sep 17 00:00:00 2001 From: Raymond Yang Date: Tue, 16 May 2023 10:02:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=A6=E4=BD=9C=E7=B4=A0=E6=9D=90=E4=B8=8A?= =?UTF-8?q?=E5=82=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + src/main/kotlin/com/ray650128/Application.kt | 9 +- .../ray650128/extension/MaterialExtension.kt | 28 ++++ .../com/ray650128/model/ErrorResponse.kt | 1 + .../com/ray650128/model/dto/MaterialDto.kt | 16 +++ .../com/ray650128/model/pojo/Material.kt | 15 +++ .../com/ray650128/model/pojo/NewMaterial.kt | 8 ++ .../ray650128/plugins/ArMaterialRouting.kt | 126 +++++++++++++++++- .../com/ray650128/service/MaterialService.kt | 45 +++++++ 9 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/com/ray650128/extension/MaterialExtension.kt create mode 100644 src/main/kotlin/com/ray650128/model/dto/MaterialDto.kt create mode 100644 src/main/kotlin/com/ray650128/model/pojo/Material.kt create mode 100644 src/main/kotlin/com/ray650128/model/pojo/NewMaterial.kt create mode 100644 src/main/kotlin/com/ray650128/service/MaterialService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 36883e6..eaaf67b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,4 +33,5 @@ dependencies { implementation("org.litote.kmongo:kmongo:$kmongo_version") implementation("io.ktor:ktor-server-auth-jvm:$ktor_version") implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktor_version") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") } \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/Application.kt b/src/main/kotlin/com/ray650128/Application.kt index 3b4ccb4..fee2959 100644 --- a/src/main/kotlin/com/ray650128/Application.kt +++ b/src/main/kotlin/com/ray650128/Application.kt @@ -2,15 +2,20 @@ package com.ray650128 import com.ray650128.model.dto.UserDto import com.ray650128.plugins.configureUserRouting -import com.ray650128.plugins.configureSerialization +import com.ray650128.plugins.configureArMaterialRouting +import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* import io.ktor.server.engine.* import io.ktor.server.netty.* +import io.ktor.server.plugins.contentnegotiation.* fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { + install(ContentNegotiation) { + json() + } install(Authentication) { jwt { verifier(JwtConfig.verifier) @@ -26,6 +31,6 @@ fun main() { } } configureUserRouting() - configureSerialization() + configureArMaterialRouting() }.start(wait = true) } diff --git a/src/main/kotlin/com/ray650128/extension/MaterialExtension.kt b/src/main/kotlin/com/ray650128/extension/MaterialExtension.kt new file mode 100644 index 0000000..1a8b5ce --- /dev/null +++ b/src/main/kotlin/com/ray650128/extension/MaterialExtension.kt @@ -0,0 +1,28 @@ +package com.ray650128.extension + +import com.ray650128.model.dto.MaterialDto +import com.ray650128.model.dto.UserDto +import com.ray650128.model.pojo.Material +import com.ray650128.model.pojo.User + +fun Material.toDto(): MaterialDto = + MaterialDto( + id = this.id.toString(), + ownerId = this.ownerId.toString(), + name = this.name, + path = this.path, + contentType = this.contentType, + fileTag = this.fileTag, + createAt = this.createAt, + updatedAt = this.updatedAt + ) + +fun MaterialDto.toMaterial(): Material = + Material( + name = this.name, + path = this.path, + contentType = this.contentType, + fileTag = this.fileTag, + createAt = this.createAt, + updatedAt = this.updatedAt + ) \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/model/ErrorResponse.kt b/src/main/kotlin/com/ray650128/model/ErrorResponse.kt index 834c387..c88dc74 100644 --- a/src/main/kotlin/com/ray650128/model/ErrorResponse.kt +++ b/src/main/kotlin/com/ray650128/model/ErrorResponse.kt @@ -7,5 +7,6 @@ data class ErrorResponse(val message: String) { companion object { val NOT_FOUND_RESPONSE = ErrorResponse("Person was not found") val BAD_REQUEST_RESPONSE = ErrorResponse("Invalid request") + val UNAUTHORIZED_RESPONSE = ErrorResponse("Unauthorized") } } \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/model/dto/MaterialDto.kt b/src/main/kotlin/com/ray650128/model/dto/MaterialDto.kt new file mode 100644 index 0000000..7f5981d --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/dto/MaterialDto.kt @@ -0,0 +1,16 @@ +package com.ray650128.model.dto + +import io.ktor.server.auth.* +import kotlinx.serialization.Serializable + +@Serializable +data class MaterialDto( + val id: String? = null, + var ownerId: String? = null, + var name: String, + val path: String, + val contentType: String, + var fileTag: ArrayList? = null, + var createAt: Long? = null, + var updatedAt: Long? = null +): Principal \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/model/pojo/Material.kt b/src/main/kotlin/com/ray650128/model/pojo/Material.kt new file mode 100644 index 0000000..9abe5aa --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/pojo/Material.kt @@ -0,0 +1,15 @@ +package com.ray650128.model.pojo + +import org.bson.codecs.pojo.annotations.BsonId +import org.litote.kmongo.Id + +data class Material( + @BsonId var id: Id? = null, + var ownerId: Id? = null, + var name: String, + var path: String, + var contentType: String, + var fileTag: ArrayList? = null, + var createAt: Long? = null, + var updatedAt: Long? = null +) \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/model/pojo/NewMaterial.kt b/src/main/kotlin/com/ray650128/model/pojo/NewMaterial.kt new file mode 100644 index 0000000..c1e14fb --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/pojo/NewMaterial.kt @@ -0,0 +1,8 @@ +package com.ray650128.model.pojo + +import kotlinx.serialization.Serializable + +@Serializable +data class NewMaterial( + var name: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/ray650128/plugins/ArMaterialRouting.kt b/src/main/kotlin/com/ray650128/plugins/ArMaterialRouting.kt index 1574ae7..1e5dad1 100644 --- a/src/main/kotlin/com/ray650128/plugins/ArMaterialRouting.kt +++ b/src/main/kotlin/com/ray650128/plugins/ArMaterialRouting.kt @@ -1,16 +1,128 @@ package com.ray650128.plugins -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.response.* +import com.ray650128.extension.* +import com.ray650128.model.ErrorResponse +import com.ray650128.model.dto.MaterialDto +import com.ray650128.model.dto.UserDto +import com.ray650128.model.pojo.Material +import com.ray650128.model.pojo.NewMaterial +import com.ray650128.service.MaterialService +import com.ray650128.service.UserService +import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlinx.serialization.json.Json +import org.litote.kmongo.Id +import java.io.File + +fun Application.configureArMaterialRouting() { + + val userService = UserService() + val materialService = MaterialService() -fun Application.configureSerialization() { - install(ContentNegotiation) { - json() - } routing { + route("/upload") { + get("/{name}") { + val fileName = call.parameters["name"] ?: run { + call.sendNotFound(ErrorResponse.NOT_FOUND_RESPONSE) + return@get + } + println("[File]filename: $fileName") + val file = File("./upload/$fileName") + println("[File]file: ${file.absolutePath}") + if(!file.exists()) { + call.sendNotFound(ErrorResponse.NOT_FOUND_RESPONSE) + return@get + } + call.respondFile(file) + } + } + + authenticate { + route("/api") { + route("/v1") { + route("/materials") { + post("/upload") { + val account = call.authentication.principal()?.account ?: run { + call.sendUnauthorized(ErrorResponse.UNAUTHORIZED_RESPONSE) + return@post + } + val user = userService.findByAccount(account) ?: run { + call.sendUnauthorized(ErrorResponse.UNAUTHORIZED_RESPONSE) + return@post + } + var materialId: String? = null + // retrieve all multipart data (suspending) + val multipart = call.receiveMultipart() + var params: NewMaterial? = null + multipart.forEachPart { part -> + // Check part type + when (part) { + is PartData.FormItem -> { + params = Json.decodeFromString(NewMaterial.serializer(), part.value) + println(part.value) + } + + is PartData.FileItem -> { + // retrieve file name of upload + println("File content type: ${part.contentType?.contentType}/${part.contentType?.contentSubtype}") + val extensionName = when (part.contentType?.contentSubtype) { + "gltf-binary" -> "glb" + "gltf+json" -> "gltf" + else -> part.contentType?.contentSubtype + } + val name = "${System.currentTimeMillis()}.$extensionName" + val directory = File("./upload/") + if (!directory.exists()) { + directory.mkdir() + } + val file = File("${directory.path}/$name") + + // use InputStream from part to save file + part.streamProvider().use { its -> + // copy the stream to the file with buffering + file.outputStream().buffered().use { + // note that this is blocking + its.copyTo(it) + } + } + + val tmpMaterial = MaterialDto( + name = if (params == null) { + part.originalFileName!! + } else { + params!!.name + }, + path = "/upload/$name", + contentType = "${part.contentType}", + createAt = System.currentTimeMillis() + ).toMaterial().apply { + ownerId = user.id!! + } + materialId = materialService.create(tmpMaterial).toString() + } + + else -> {} + } + // make sure to dispose of the part after use to prevent leaks + part.dispose() + } + if (materialId != null) { + val data = materialService.findById(materialId!!)?.toDto() + call.sendSuccess(data) + } else { + call.sendBadRequest(ErrorResponse("Add material fail.")) + } + } + } + } + } + } + get("/json/kotlinx-serialization") { call.respond(mapOf("hello" to "world")) } diff --git a/src/main/kotlin/com/ray650128/service/MaterialService.kt b/src/main/kotlin/com/ray650128/service/MaterialService.kt new file mode 100644 index 0000000..7941a74 --- /dev/null +++ b/src/main/kotlin/com/ray650128/service/MaterialService.kt @@ -0,0 +1,45 @@ +package com.ray650128.service + +import com.ray650128.model.pojo.Material +import org.bson.types.ObjectId +import org.litote.kmongo.* +import org.litote.kmongo.id.toId + +class MaterialService { + private val client = KMongo.createClient("mongodb://www.ray650128.com:27017") + private val database = client.getDatabase("ar_system") + private val userCollection = database.getCollection() + + fun create(user: Material): Id? { + userCollection.insertOne(user) + return user.id + } + + fun findAll(): List = userCollection.find().toList() + + fun findById(id: String): Material? { + val bsonId: Id = ObjectId(id).toId() + return userCollection.findOne(Material::id eq bsonId) + } + + fun findByName(name: String): List { + val caseSensitiveTypeSafeFilter = Material::name regex name + return userCollection.find(caseSensitiveTypeSafeFilter).toList() + } + + fun updateById(id: String, request: Material): Boolean = + findById(id)?.let { user -> + val updateResult = userCollection.replaceOne( + user.copy( + name = request.name, + updatedAt = request.updatedAt + ) + ) + updateResult.modifiedCount == 1L + } ?: false + + fun deleteById(id: String): Boolean { + val deleteResult = userCollection.deleteOneById(ObjectId(id)) + return deleteResult.deletedCount == 1L + } +} \ No newline at end of file