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
|
* edit document data
|
||||||
* parse document contents (for pdf, txt, ...)
|
* parse document contents (for pdf, txt, ...)
|
||||||
* guess target date from filenames
|
* guess target date from filenames
|
||||||
|
* move documents to trash
|
||||||
|
* delete documents + files
|
||||||
|
* delete files in limbo
|
||||||
|
|
||||||
## Build
|
## 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
|
package net.h34t.filemure
|
||||||
|
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import net.h34t.filemure.controller.LimboController
|
import net.h34t.filemure.controller.*
|
||||||
import net.h34t.filemure.controller.LoginController
|
|
||||||
import net.h34t.filemure.controller.OverviewController
|
|
||||||
import net.h34t.filemure.controller.UploadController
|
|
||||||
import net.h34t.filemure.repository.SqliteRepository
|
import net.h34t.filemure.repository.SqliteRepository
|
||||||
|
|
||||||
class FilemureApp(repository: SqliteRepository) {
|
class FilemureApp(repository: SqliteRepository) {
|
||||||
|
@ -16,6 +13,7 @@ class FilemureApp(repository: SqliteRepository) {
|
||||||
private val overviewController = OverviewController(modifiers, repository)
|
private val overviewController = OverviewController(modifiers, repository)
|
||||||
private val limboController = LimboController(modifiers, repository)
|
private val limboController = LimboController(modifiers, repository)
|
||||||
private val uploadController = UploadController(modifiers, repository)
|
private val uploadController = UploadController(modifiers, repository)
|
||||||
|
private val documentController = DocumentController(modifiers, repository)
|
||||||
|
|
||||||
fun register(server: Javalin) {
|
fun register(server: Javalin) {
|
||||||
server.get("/") { ctx ->
|
server.get("/") { ctx ->
|
||||||
|
@ -31,5 +29,7 @@ class FilemureApp(repository: SqliteRepository) {
|
||||||
|
|
||||||
server.post("/upload", uploadController::upload)
|
server.post("/upload", uploadController::upload)
|
||||||
server.get("/limbo", limboController::formLimbo)
|
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() {
|
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
|
Javalin
|
||||||
.create { config ->
|
.create { config ->
|
||||||
|
|
|
@ -2,10 +2,11 @@ package net.h34t.filemure
|
||||||
|
|
||||||
import net.h34t.filemure.tpl.Frame
|
import net.h34t.filemure.tpl.Frame
|
||||||
import net.h34t.filemure.tpl.Limbo
|
import net.h34t.filemure.tpl.Limbo
|
||||||
|
import net.h34t.filemure.tpl.NewDocumentForm
|
||||||
import org.apache.commons.text.StringEscapeUtils
|
import org.apache.commons.text.StringEscapeUtils
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers {
|
class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, NewDocumentForm.Modifiers {
|
||||||
|
|
||||||
fun hashPrefix(arg: String): String {
|
fun hashPrefix(arg: String): String {
|
||||||
return URLEncoder.encode(arg, Charsets.UTF_8)
|
return URLEncoder.encode(arg, Charsets.UTF_8)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.h34t.filemure
|
||||||
import io.javalin.http.ContentType
|
import io.javalin.http.ContentType
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.HttpStatus
|
import io.javalin.http.HttpStatus
|
||||||
|
import io.javalin.http.UnauthorizedResponse
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@ fun Context.redirectPRG(location: String) = this.redirect(location, HttpStatus.S
|
||||||
fun Context.getSession() = this.sessionAttribute<Session>("session")
|
fun Context.getSession() = this.sessionAttribute<Session>("session")
|
||||||
fun Context.setSession(session: Session?) = 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')
|
private val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 io.javalin.http.Context
|
||||||
import net.h34t.filemure.TemplateModifiers
|
import net.h34t.filemure.TemplateModifiers
|
||||||
import net.h34t.filemure.getSession
|
|
||||||
import net.h34t.filemure.repository.SqliteRepository
|
import net.h34t.filemure.repository.SqliteRepository
|
||||||
|
import net.h34t.filemure.requireSession
|
||||||
import net.h34t.filemure.tempolin
|
import net.h34t.filemure.tempolin
|
||||||
import net.h34t.filemure.tpl.Frame
|
import net.h34t.filemure.tpl.Frame
|
||||||
import net.h34t.filemure.tpl.Limbo
|
import net.h34t.filemure.tpl.Limbo
|
||||||
|
@ -13,23 +13,17 @@ import java.time.format.FormatStyle
|
||||||
class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||||
|
|
||||||
private val dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
|
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) {
|
fun formLimbo(ctx: Context) {
|
||||||
val session = ctx.getSession()
|
val session = ctx.requireSession()
|
||||||
requireNotNull(session)
|
|
||||||
|
|
||||||
|
|
||||||
val files = repository.getFilesInLimbo(accountId = session.id)
|
val files = repository.getFilesInLimbo(accountId = session.id)
|
||||||
|
|
||||||
ctx.tempolin(
|
ctx.tempolin(
|
||||||
Frame(
|
Frame(
|
||||||
modifiers = modifiers,
|
modifiers = modifiers, title = "Filemure Limbo", isTarget = true, content = Limbo(
|
||||||
title = "Filemure Limbo",
|
modifiers = modifiers, limboFileCount = files.size.toString(), file = {
|
||||||
isTarget = true,
|
|
||||||
content = Limbo(
|
|
||||||
modifiers = modifiers,
|
|
||||||
limboFileCount = files.size.toString(),
|
|
||||||
file = {
|
|
||||||
files.map { f ->
|
files.map { f ->
|
||||||
FileBlock(
|
FileBlock(
|
||||||
id = f.id.toString(),
|
id = f.id.toString(),
|
||||||
|
@ -39,8 +33,7 @@ class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRe
|
||||||
uploaded = f.created.format(dtf)
|
uploaded = f.created.format(dtf)
|
||||||
)
|
)
|
||||||
}.asSequence()
|
}.asSequence()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ package net.h34t.filemure.controller
|
||||||
|
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import net.h34t.filemure.TemplateModifiers
|
import net.h34t.filemure.TemplateModifiers
|
||||||
import net.h34t.filemure.getSession
|
|
||||||
import net.h34t.filemure.repository.SqliteRepository
|
import net.h34t.filemure.repository.SqliteRepository
|
||||||
|
import net.h34t.filemure.requireSession
|
||||||
import net.h34t.filemure.tempolin
|
import net.h34t.filemure.tempolin
|
||||||
import net.h34t.filemure.tpl.Frame
|
import net.h34t.filemure.tpl.Frame
|
||||||
import net.h34t.filemure.tpl.Overview
|
import net.h34t.filemure.tpl.Overview
|
||||||
|
@ -11,9 +11,7 @@ import net.h34t.filemure.tpl.Overview
|
||||||
class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||||
|
|
||||||
fun overview(ctx: Context) {
|
fun overview(ctx: Context) {
|
||||||
val session = ctx.getSession()
|
val session = ctx.requireSession()
|
||||||
requireNotNull(session)
|
|
||||||
|
|
||||||
|
|
||||||
val limboFileCount = repository.getLimboFileCount(accountId = session.id)
|
val limboFileCount = repository.getLimboFileCount(accountId = session.id)
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,13 @@ package net.h34t.filemure.controller
|
||||||
|
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import net.h34t.filemure.TemplateModifiers
|
import net.h34t.filemure.TemplateModifiers
|
||||||
import net.h34t.filemure.getSession
|
|
||||||
import net.h34t.filemure.repository.SqliteRepository
|
import net.h34t.filemure.repository.SqliteRepository
|
||||||
|
import net.h34t.filemure.requireSession
|
||||||
|
|
||||||
class UploadController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
class UploadController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||||
|
|
||||||
fun upload(ctx: Context) {
|
fun upload(ctx: Context) {
|
||||||
println("upload")
|
val session = ctx.requireSession()
|
||||||
val session = ctx.getSession()
|
|
||||||
requireNotNull(session)
|
|
||||||
|
|
||||||
val accountid = session.id
|
val accountid = session.id
|
||||||
|
|
||||||
|
|
|
@ -61,4 +61,16 @@ class SqliteRepository(url: String) {
|
||||||
return list
|
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>
|
<h1>Filemure Limbo</h1>
|
||||||
|
|
||||||
<p>{$limboFileCount} Files</p>
|
<p>{$limboFileCount} Files</p>
|
||||||
<form action="/document" method="post">
|
<form action="/document/new" method="get">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>-</th>
|
<th>-</th>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{for $file}
|
{for $file}
|
||||||
<tr>
|
<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>{*$file}</td>
|
||||||
<td>{*$type}</td>
|
<td>{*$type}</td>
|
||||||
<td>{*$size}</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