diff --git a/README.md b/README.md index 01a4eba..734abf7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ It supports: * edit document data * parse document contents (for pdf, txt, ...) * guess target date from filenames +* move documents to trash +* delete documents + files +* delete files in limbo ## Build diff --git a/app/src/main/kotlin/net/h34t/filemure/DateGuesser.kt b/app/src/main/kotlin/net/h34t/filemure/DateGuesser.kt new file mode 100644 index 0000000..73b6e6e --- /dev/null +++ b/app/src/main/kotlin/net/h34t/filemure/DateGuesser.kt @@ -0,0 +1,54 @@ +package net.h34t.filemure + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +object DateGuesser { + + private val dateTimePatterns = listOf( + Regex("\\d{4}-\\d{2}-\\d{2}_\\d{2}:\\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd'_'HH:mm:ss"), + Regex("\\d{4}-\\d{2}-\\d{2}_\\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd'_'HH:mm"), + + Regex("\\d{4}-\\d{2}-\\d{2}-\\d{2}:\\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd'-'HH:mm:ss"), + Regex("\\d{4}-\\d{2}-\\d{2}-\\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd'-'HH:mm"), + + Regex("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd' 'HH:mm:ss"), + Regex("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd' 'HH:mm"), + + // --- + + Regex("\\d{4}\\d{2}\\d{2}_\\d{2}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd'_'HHmmss"), + Regex("\\d{4}\\d{2}\\d{2}_\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd'_'HHmm"), + + Regex("\\d{4}\\d{2}\\d{2}-\\d{2}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd'-'HHmmss"), + Regex("\\d{4}\\d{2}\\d{2}-\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd'-'HHmm"), + + Regex("\\d{4}\\d{2}\\d{2} \\d{2}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd' 'HHmmss"), + Regex("\\d{4}\\d{2}\\d{2} \\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd' 'HHmm"), + + Regex("\\d{4}\\d{2}\\d{2}\\d{2}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd' 'HHmmss"), + Regex("\\d{4}\\d{2}\\d{2}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd' 'HHmm"), + ) + + private val datePatterns = listOf( + Regex("\\d{4}-\\d{2}-\\d{2}") to DateTimeFormatter.ofPattern("yyyy-MM-dd"), + Regex("\\d{4}_\\d{2}_\\d{2}") to DateTimeFormatter.ofPattern("yyyy_MM_dd"), + Regex("\\d{4}\\d{2}\\d{2}") to DateTimeFormatter.ofPattern("yyyyMMdd"), + Regex("\\d{2}.\\d{2}.\\d{4}") to DateTimeFormatter.ofPattern("dd.MM.YYYY"), + Regex("\\d{2}.\\d{2}.\\d{2}") to DateTimeFormatter.ofPattern("dd.MM.yy"), + + ) + + fun guessDateTime(filename: String) = dateTimePatterns.asSequence().mapNotNull { + it.first.find(filename)?.let { mr -> LocalDateTime.parse(mr.groups[1]?.value!!, it.second) } + }.firstOrNull() + + fun guessDate(filename: String) = dateTimePatterns.asSequence().mapNotNull { + it.first.find(filename)?.let { mr -> LocalDate.parse(mr.groups[1]?.value!!, it.second) } + }.firstOrNull() + + fun guess(filename: String): LocalDateTime? { + return guessDateTime(filename) ?: guessDate(filename)?.atTime(0, 0) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt b/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt index cc1fb19..c75201f 100644 --- a/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt +++ b/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt @@ -1,10 +1,7 @@ package net.h34t.filemure import io.javalin.Javalin -import net.h34t.filemure.controller.LimboController -import net.h34t.filemure.controller.LoginController -import net.h34t.filemure.controller.OverviewController -import net.h34t.filemure.controller.UploadController +import net.h34t.filemure.controller.* import net.h34t.filemure.repository.SqliteRepository class FilemureApp(repository: SqliteRepository) { @@ -16,6 +13,7 @@ class FilemureApp(repository: SqliteRepository) { private val overviewController = OverviewController(modifiers, repository) private val limboController = LimboController(modifiers, repository) private val uploadController = UploadController(modifiers, repository) + private val documentController = DocumentController(modifiers, repository) fun register(server: Javalin) { server.get("/") { ctx -> @@ -31,5 +29,7 @@ class FilemureApp(repository: SqliteRepository) { server.post("/upload", uploadController::upload) server.get("/limbo", limboController::formLimbo) + server.get("/document/new", documentController::createDocumentForm) + server.post("/document/new", documentController::createDocument) } } \ No newline at end of file diff --git a/app/src/main/kotlin/net/h34t/filemure/Server.kt b/app/src/main/kotlin/net/h34t/filemure/Server.kt index b396cd4..3db3f73 100644 --- a/app/src/main/kotlin/net/h34t/filemure/Server.kt +++ b/app/src/main/kotlin/net/h34t/filemure/Server.kt @@ -7,8 +7,10 @@ import net.h34t.filemure.repository.SqliteRepository fun main() { + val db = System.getenv("dbpath") + ?: throw IllegalArgumentException("Please define an env dbpath, e.g. /data/filemure.db") - val app = FilemureApp(SqliteRepository("jdbc:sqlite:filemure.db")) + val app = FilemureApp(SqliteRepository("jdbc:sqlite:$db")) Javalin .create { config -> diff --git a/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt b/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt index 1ed8224..8599ac2 100644 --- a/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt +++ b/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt @@ -2,10 +2,11 @@ package net.h34t.filemure import net.h34t.filemure.tpl.Frame import net.h34t.filemure.tpl.Limbo +import net.h34t.filemure.tpl.NewDocumentForm import org.apache.commons.text.StringEscapeUtils import java.net.URLEncoder -class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers { +class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, NewDocumentForm.Modifiers { fun hashPrefix(arg: String): String { return URLEncoder.encode(arg, Charsets.UTF_8) diff --git a/app/src/main/kotlin/net/h34t/filemure/Util.kt b/app/src/main/kotlin/net/h34t/filemure/Util.kt index 577d2a3..b2279d2 100644 --- a/app/src/main/kotlin/net/h34t/filemure/Util.kt +++ b/app/src/main/kotlin/net/h34t/filemure/Util.kt @@ -3,6 +3,7 @@ package net.h34t.filemure import io.javalin.http.ContentType import io.javalin.http.Context import io.javalin.http.HttpStatus +import io.javalin.http.UnauthorizedResponse import java.io.BufferedOutputStream import java.io.OutputStreamWriter @@ -33,9 +34,11 @@ fun Context.redirectPRG(location: String) = this.redirect(location, HttpStatus.S fun Context.getSession() = this.sessionAttribute("session") fun Context.setSession(session: Session?) = this.sessionAttribute("session", session) +fun Context.requireSession(): Session = this.getSession() ?: throw UnauthorizedResponse("Not logged in") + private val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9') fun generateExtId(): String { return (0..8).map { chars.random() }.joinToString("") -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt new file mode 100644 index 0000000..2e0b522 --- /dev/null +++ b/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt @@ -0,0 +1,80 @@ +package net.h34t.filemure.controller + +import io.javalin.http.BadRequestResponse +import io.javalin.http.Context +import io.javalin.http.ForbiddenResponse +import net.h34t.filemure.* +import net.h34t.filemure.repository.SqliteRepository +import net.h34t.filemure.tpl.Frame +import net.h34t.filemure.tpl.NewDocumentForm +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle + +class DocumentController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { + + private val dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT) + private val isoDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") + + fun createDocumentForm(ctx: Context) { + val session = ctx.requireSession() + + val fileIds = ctx.queryParams("file_id").map { it.toLong() } + + val limboFiles = repository.getFilesInLimbo(session.id) + val limboFileIds = limboFiles.map { it.id } + + if (!fileIds.all { it in limboFileIds }) { + throw ForbiddenResponse("Mismatched file ids.") + } + + val selectedFiles = limboFiles.filter { it.id in fileIds } + + val title = "new document" + val referenceDate = selectedFiles.asSequence().mapNotNull { DateGuesser.guess(it.filename) }.firstOrNull() + ?: LocalDateTime.now() + val tags = selectedFiles.map { File(it.filename).extension }.distinct().asSequence() + val description = "" + + ctx.tempolin( + Frame( + modifiers = modifiers, title = "new document", isTarget = true, content = NewDocumentForm( + modifiers = modifiers, + title = title, + referenceDate = isoDtf.format(referenceDate), + tags = { tags.map { TagsBlock(it) } }, + description = description, + files = { + selectedFiles + .map { + FilesBlock( + id = it.id.toString(), + filename = it.filename, + contentType = it.contentType + ) + } + .asSequence() + }) + ) + ) + } + + fun createDocument(ctx: Context) { + val session = ctx.requireSession() + + val title = ctx.formParam("title") + ?: throw BadRequestResponse("") + val referenceDate = ctx.formParam("reference_date")?.let { LocalDateTime.parse(it, isoDtf) } + ?: throw BadRequestResponse("") + val tags = ctx.formParam("tags")?.split("\\s+") + ?: emptyList() + val description = ctx.formParam("description") + ?: throw BadRequestResponse("") + val fileIds = ctx.formParams("file_id").map { it.toLong() } + + val id = repository.addDocument(session.id, title, referenceDate, tags, description, fileIds) + + ctx.redirectPRG("/document/$id") + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/LimboController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/LimboController.kt index d3da439..7828a11 100644 --- a/app/src/main/kotlin/net/h34t/filemure/controller/LimboController.kt +++ b/app/src/main/kotlin/net/h34t/filemure/controller/LimboController.kt @@ -2,8 +2,8 @@ package net.h34t.filemure.controller import io.javalin.http.Context import net.h34t.filemure.TemplateModifiers -import net.h34t.filemure.getSession import net.h34t.filemure.repository.SqliteRepository +import net.h34t.filemure.requireSession import net.h34t.filemure.tempolin import net.h34t.filemure.tpl.Frame import net.h34t.filemure.tpl.Limbo @@ -13,23 +13,17 @@ import java.time.format.FormatStyle class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { private val dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT) + private val isoDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") fun formLimbo(ctx: Context) { - val session = ctx.getSession() - requireNotNull(session) - + val session = ctx.requireSession() val files = repository.getFilesInLimbo(accountId = session.id) ctx.tempolin( Frame( - modifiers = modifiers, - title = "Filemure Limbo", - isTarget = true, - content = Limbo( - modifiers = modifiers, - limboFileCount = files.size.toString(), - file = { + modifiers = modifiers, title = "Filemure Limbo", isTarget = true, content = Limbo( + modifiers = modifiers, limboFileCount = files.size.toString(), file = { files.map { f -> FileBlock( id = f.id.toString(), @@ -39,8 +33,7 @@ class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRe uploaded = f.created.format(dtf) ) }.asSequence() - } - ) + }) ) ) } diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt index 8dddc2d..63dfca8 100644 --- a/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt +++ b/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt @@ -2,8 +2,8 @@ package net.h34t.filemure.controller import io.javalin.http.Context import net.h34t.filemure.TemplateModifiers -import net.h34t.filemure.getSession import net.h34t.filemure.repository.SqliteRepository +import net.h34t.filemure.requireSession import net.h34t.filemure.tempolin import net.h34t.filemure.tpl.Frame import net.h34t.filemure.tpl.Overview @@ -11,9 +11,7 @@ import net.h34t.filemure.tpl.Overview class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { fun overview(ctx: Context) { - val session = ctx.getSession() - requireNotNull(session) - + val session = ctx.requireSession() val limboFileCount = repository.getLimboFileCount(accountId = session.id) diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/UploadController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/UploadController.kt index 701e077..15ee45d 100644 --- a/app/src/main/kotlin/net/h34t/filemure/controller/UploadController.kt +++ b/app/src/main/kotlin/net/h34t/filemure/controller/UploadController.kt @@ -2,15 +2,13 @@ package net.h34t.filemure.controller import io.javalin.http.Context import net.h34t.filemure.TemplateModifiers -import net.h34t.filemure.getSession import net.h34t.filemure.repository.SqliteRepository +import net.h34t.filemure.requireSession class UploadController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { fun upload(ctx: Context) { - println("upload") - val session = ctx.getSession() - requireNotNull(session) + val session = ctx.requireSession() val accountid = session.id diff --git a/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt b/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt index 774327e..53f810b 100644 --- a/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt +++ b/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt @@ -61,4 +61,16 @@ class SqliteRepository(url: String) { return list } } + + fun addDocument( + accountId: Long, + title: String, + referenceDate: LocalDateTime, + tags: List, + description: String, + fileIds: List + ): Long { + TODO("Not implemented") + return 1L; + } } \ No newline at end of file diff --git a/app/src/main/tpl/net.h34t.filemure.tpl/Limbo.tpl.html b/app/src/main/tpl/net.h34t.filemure.tpl/Limbo.tpl.html index 72cbdac..3acd956 100644 --- a/app/src/main/tpl/net.h34t.filemure.tpl/Limbo.tpl.html +++ b/app/src/main/tpl/net.h34t.filemure.tpl/Limbo.tpl.html @@ -1,7 +1,7 @@

Filemure Limbo

{$limboFileCount} Files

-
+ @@ -12,7 +12,7 @@ {for $file} - + diff --git a/app/src/main/tpl/net.h34t.filemure.tpl/NewDocumentForm.tpl.html b/app/src/main/tpl/net.h34t.filemure.tpl/NewDocumentForm.tpl.html new file mode 100644 index 0000000..2727dac --- /dev/null +++ b/app/src/main/tpl/net.h34t.filemure.tpl/NewDocumentForm.tpl.html @@ -0,0 +1,24 @@ + +

+ +

+ +

+
{for $tags}{*$tag}{/for}
+ +

+ +
    + {for $files} +
  • {*$filename} ({*$contentType}) +
  • + {/for} +
+ +

+ \ No newline at end of file
-
{*$file} {*$type} {*$size}