Adds more document creation groundwork.
This commit is contained in:
parent
49abfa3663
commit
fae33db529
13 changed files with 198 additions and 30 deletions
|
@ -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
|
||||
|
||||
|
|
54
app/src/main/kotlin/net/h34t/filemure/DateGuesser.kt
Normal file
54
app/src/main/kotlin/net/h34t/filemure/DateGuesser.kt
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue