Adds more document creation groundwork.

This commit is contained in:
Stefan Schallerl 2025-02-02 17:17:52 +01:00
parent 49abfa3663
commit fae33db529
13 changed files with 198 additions and 30 deletions

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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 ->

View file

@ -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)

View file

@ -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>("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("")
}
}

View file

@ -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")
}
}

View file

@ -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()
}
)
})
)
)
}

View file

@ -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)

View file

@ -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

View file

@ -61,4 +61,16 @@ class SqliteRepository(url: String) {
return list
}
}
fun addDocument(
accountId: Long,
title: String,
referenceDate: LocalDateTime,
tags: List<String>,
description: String,
fileIds: List<Long>
): Long {
TODO("Not implemented")
return 1L;
}
}

View file

@ -1,7 +1,7 @@
<h1>Filemure Limbo</h1>
<p>{$limboFileCount} Files</p>
<form action="/document" method="post">
<form action="/document/new" method="get">
<table>
<tr>
<th>-</th>
@ -12,7 +12,7 @@
</tr>
{for $file}
<tr>
<td><label><input type="checkbox" name="limbofile" value="{*$id}"></label></td>
<td><label><input type="checkbox" name="file_id" value="{*$id}"></label></td>
<td>{*$file}</td>
<td>{*$type}</td>
<td>{*$size}</td>

View file

@ -0,0 +1,24 @@
<form action="/document/new" method="post">
<p><label>Document title:<br>
<input type="text" name="title" value="{*$title}"></label></p>
<p><label>Date:<br>
<input type="datetime-local" name="reference_date" value="{*$referenceDate}"></label></p>
<p><label>Tags:<br>
<input type="text" name="tags"></label></p>
<div id="tags">{for $tags}<span>{*$tag}</span>{/for}</div>
<p><label>Description:<br>
<textarea name="description" rows="40" cols="80">{*$description}</textarea>
</label></p>
<ul>
{for $files}
<li>{*$filename} ({*$contentType})
<input type="hidden" name="file_id" value="{$id}"></li>
{/for}
</ul>
<p><input type="submit" value="create"></p>
</form>