Skip to content

Commit

Permalink
Merge pull request #4 from LotuxPunk/feat/public-content
Browse files Browse the repository at this point in the history
Public content and caching
  • Loading branch information
LotuxPunk authored Nov 3, 2024
2 parents b9864e3 + c00089f commit 72ef3df
Show file tree
Hide file tree
Showing 15 changed files with 480 additions and 267 deletions.
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ RUN mkdir /app
COPY --from=build /home/gradle/src/build/libs/*.jar /app/hestia.jar
ENV API_KEY=
ENV BASE_DIRECTORY=
ENV PUBLIC_DIRECTORY=
ENV JWT_AUDIENCE=
ENV JWT_ISSUER=
ENV JWT_REALM=
ENV JWT_SECRET=
ENTRYPOINT ["java","-jar","/app/hestia.jar"]
LABEL org.opencontainers.image.source=https://github.com/LotuxPunk/Hestia
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies {
implementation("io.ktor:ktor-server-status-pages:$ktor_version")
implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
implementation("io.ktor:ktor-server-caching-headers:$ktor_version")

// Cache4k
implementation("io.github.reactivecircus.cache4k:cache4k:0.13.0")
Expand All @@ -58,5 +59,6 @@ dependencies {
testImplementation("io.ktor:ktor-client-content-negotiation-jvm")
testImplementation("io.ktor:ktor-client-core:$ktor_version")
testImplementation("io.ktor:ktor-client-cio:$ktor_version")
testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
}
8 changes: 5 additions & 3 deletions src/main/kotlin/be/vandeas/controller/v1/FileController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ fun Route.fileControllerV1() = route("/file") {
if (fileName == null) {
val options = DirectoryDeleteOptions(
path = path,
recursive = recursive
recursive = recursive,
public = false
)

when (val result = fileService.deleteDirectory(authorization, options)) {
Expand All @@ -153,8 +154,9 @@ fun Route.fileControllerV1() = route("/file") {
}
} else {
val options = FileDeleteOptions(
path = call.request.queryParameters["path"] ?: throw IllegalArgumentException("path query parameter is required"),
fileName = call.request.queryParameters["fileName"] ?: ""
path = path,
fileName = fileName,
public = false
)

when (val result = fileService.deleteFile(authorization, options)) {
Expand Down
61 changes: 48 additions & 13 deletions src/main/kotlin/be/vandeas/controller/v2/FileController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.inject
import java.nio.file.Paths
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.days

fun Route.fileControllerV2() = route("/file") {

Expand Down Expand Up @@ -64,10 +69,17 @@ fun Route.fileControllerV2() = route("/file") {

is FileCreationResult.Failure -> call.respond(HttpStatusCode.InternalServerError, result.message)
is FileCreationResult.NotFound -> call.respond(HttpStatusCode.NotFound, mapOf("path" to options.path))
is FileCreationResult.Success -> call.respond(
HttpStatusCode.Created,
FileNameWithPath(path = options.path, fileName = options.fileName)
)
is FileCreationResult.Success -> {
val resultPath = result.path.parent.pathString.replace(
"${if (options.public) System.getenv("PUBLIC_DIRECTORY") else System.getenv("BASE_DIRECTORY")}/",
if (options.public) "public" else ""
)

call.respond(
HttpStatusCode.Created,
FileNameWithPath(path = resultPath, fileName = options.fileName)
)
}
}
}

Expand All @@ -77,13 +89,15 @@ fun Route.fileControllerV2() = route("/file") {
var fileName: String? = null
var path: String? = null
var data: ByteArray? = null
var public = false

multipart.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
when (part.name) {
"path" -> path = part.value
"fileName" -> fileName = part.value
"public" -> public = part.value.toBoolean()
}
}

Expand All @@ -103,7 +117,8 @@ fun Route.fileControllerV2() = route("/file") {
val options = BytesFileCreationOptions(
path = path!!,
fileName = fileName!!,
content = data!!
content = data!!,
public = public
)

when (val result = fileLogic.createFile(options)) {
Expand All @@ -114,10 +129,17 @@ fun Route.fileControllerV2() = route("/file") {

is FileCreationResult.Failure -> call.respond(HttpStatusCode.InternalServerError, result.message)
is FileCreationResult.NotFound -> call.respond(HttpStatusCode.NotFound, mapOf("path" to options.path))
is FileCreationResult.Success -> call.respond(
HttpStatusCode.Created,
FileNameWithPath(path = options.path, fileName = options.fileName)
)
is FileCreationResult.Success -> {
val resultPath = result.path.parent.pathString.replace(
"${if (options.public) System.getenv("PUBLIC_DIRECTORY") else System.getenv("BASE_DIRECTORY")}/",
if (options.public) "public/" else ""
)

call.respond(
HttpStatusCode.Created,
FileNameWithPath(path = resultPath, fileName = options.fileName)
)
}
}

}
Expand All @@ -127,11 +149,13 @@ fun Route.fileControllerV2() = route("/file") {
?: throw IllegalArgumentException("path query parameter is required")
val fileName = call.request.queryParameters["fileName"]
val recursive = call.request.queryParameters["recursive"]?.toBoolean() ?: false
val public = call.request.queryParameters["public"]?.toBoolean() ?: false

if (fileName == null) {
val options = DirectoryDeleteOptions(
path = path,
recursive = recursive
recursive = recursive,
public = public
)

when (val result = fileLogic.deleteDirectory(options)) {
Expand All @@ -155,9 +179,9 @@ fun Route.fileControllerV2() = route("/file") {
}
} else {
val options = FileDeleteOptions(
path = call.request.queryParameters["path"]
?: throw IllegalArgumentException("path query parameter is required"),
fileName = call.request.queryParameters["fileName"] ?: ""
path = path,
fileName = fileName,
public = public,
)

when (val result = fileLogic.deleteFile(options)) {
Expand All @@ -177,7 +201,18 @@ fun Route.fileControllerV2() = route("/file") {
}
}
}
staticFiles("/public", Paths.get(System.getenv("PUBLIC_DIRECTORY")).toFile()) {
cacheControl { file ->
when(file.extension.lowercase()) {
"jpg", "jpeg", "png", "gif" -> listOf(CacheControl.MaxAge(maxAgeSeconds = 30.days.inWholeSeconds.toInt()))
"pdf" -> listOf(CacheControl.MaxAge(maxAgeSeconds = 30.days.inWholeSeconds.toInt()))
else -> emptyList()
}
}
}
get("/embed") {
call.caching = CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 30.days.inWholeSeconds.toInt(), visibility = CacheControl.Visibility.Private))

val path = call.request.queryParameters["path"] ?: ""
val fileName = call.request.queryParameters["fileName"] ?: ""
val downloadFileName = call.request.queryParameters["download"] ?: ""
Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/be/vandeas/dto/Base64FileCreationOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import kotlinx.serialization.Serializable

@Serializable
data class Base64FileCreationOptions(
val path: String,
val fileName: String,
override val path: String,
override val fileName: String,
override val public: Boolean = false,
val content: String,
)
): FileOperationOptions
7 changes: 4 additions & 3 deletions src/main/kotlin/be/vandeas/dto/BytesFileCreationOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import kotlinx.serialization.Serializable

@Serializable
data class BytesFileCreationOptions(
val path: String,
val fileName: String,
override val path: String,
override val fileName: String,
override val public: Boolean = false,
val content: ByteArray,
)
): FileOperationOptions
3 changes: 2 additions & 1 deletion src/main/kotlin/be/vandeas/dto/DirectoryDeleteOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
data class DirectoryDeleteOptions(
val path: String,
val recursive: Boolean = false,
)
override val public: Boolean,
): FileVisibilityOptions
7 changes: 4 additions & 3 deletions src/main/kotlin/be/vandeas/dto/FileDeleteOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable

@Serializable
data class FileDeleteOptions(
val path: String,
val fileName: String,
)
override val path: String,
override val fileName: String,
override val public: Boolean,
): FileOperationOptions
6 changes: 6 additions & 0 deletions src/main/kotlin/be/vandeas/dto/FileOperationOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package be.vandeas.dto

interface FileOperationOptions: FileVisibilityOptions {
val path: String
val fileName: String
}
5 changes: 5 additions & 0 deletions src/main/kotlin/be/vandeas/dto/FileVisibilityOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package be.vandeas.dto

interface FileVisibilityOptions {
val public: Boolean
}
6 changes: 4 additions & 2 deletions src/main/kotlin/be/vandeas/handler/FileHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import java.nio.file.*
import kotlin.io.FileAlreadyExistsException
import kotlin.io.path.*

object FileHandler {
class FileHandler(
directory: String
) {
private val LOGGER = KtorSimpleLogger("be.vandeas.handlers.FileHandler")
private val BASE_DIRECTORY: Path = Path.of(URI.create("file://${System.getenv("BASE_DIRECTORY")}"))
private val BASE_DIRECTORY: Path = Path.of(URI.create("file://$directory"))

/**
* Writes a file.
Expand Down
20 changes: 13 additions & 7 deletions src/main/kotlin/be/vandeas/logic/impl/FileLogicImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,48 @@ import be.vandeas.logic.FileLogic
import io.ktor.util.*
import java.nio.file.Paths

class FileLogicImpl : FileLogic {
class FileLogicImpl(
private val privateFileHandler: FileHandler,
private val publicFileHandler: FileHandler
) : FileLogic {

private fun FileVisibilityOptions.fileHandler() = if (public) publicFileHandler else privateFileHandler

override fun createFile(options: Base64FileCreationOptions): FileCreationResult {
return FileHandler.write(
return options.fileHandler().write(
content = options.content.decodeBase64Bytes(),
filePath = Paths.get(options.path, options.fileName)
)
}

override fun createFile(options: BytesFileCreationOptions): FileCreationResult {
return FileHandler.write(
return options.fileHandler().write(
content = options.content,
filePath = Paths.get(options.path, options.fileName)
)
}

override fun deleteFile(fileDeleteOptions: FileDeleteOptions): FileDeleteResult {
return FileHandler.deleteFile(
return fileDeleteOptions.fileHandler().deleteFile(
path = Paths.get(fileDeleteOptions.path, fileDeleteOptions.fileName)
)
}

override fun deleteDirectory(directoryDeleteOptions: DirectoryDeleteOptions): DirectoryDeleteResult {
return FileHandler.deleteDirectory(
return directoryDeleteOptions.fileHandler().deleteDirectory(
path = Paths.get(directoryDeleteOptions.path),
recursive = directoryDeleteOptions.recursive
)
}

override fun readFile(fileReadOptions: FileReadOptions): FileBytesReadResult {
return FileHandler.read(
return privateFileHandler.read(
path = Paths.get(fileReadOptions.path, fileReadOptions.fileName)
)
}

override fun getFile(fileReadOptions: FileReadOptions): FileReadResult {
return FileHandler.get(
return privateFileHandler.get(
path = Paths.get(fileReadOptions.path, fileReadOptions.fileName)
)
}
Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/be/vandeas/plugins/KoinConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package be.vandeas.plugins

import be.vandeas.handler.FileHandler
import be.vandeas.logic.AuthLogic
import be.vandeas.logic.FileLogic
import be.vandeas.logic.impl.AuthLogicImpl
Expand All @@ -18,7 +19,10 @@ import org.koin.logger.slf4jLogger

fun appModule(environment: ApplicationEnvironment) = module {
single<FileLogic> {
FileLogicImpl()
FileLogicImpl(
privateFileHandler = FileHandler(System.getenv("BASE_DIRECTORY")),
publicFileHandler = FileHandler(System.getenv("PUBLIC_DIRECTORY")),
)
}

single<AuthLogic> {
Expand Down
Loading

0 comments on commit 72ef3df

Please sign in to comment.