Adds document search.

This commit is contained in:
Stefan Schallerl 2025-02-06 22:54:51 +01:00
parent c44fa80d7f
commit 3045f46525
11 changed files with 129 additions and 13 deletions

View file

@ -10,6 +10,7 @@ plugins {
} }
dependencies { dependencies {
implementation("org.apache.pdfbox:pdfbox:3.0.4")
implementation("app.cash.sqldelight:sqlite-driver:2.0.2") implementation("app.cash.sqldelight:sqlite-driver:2.0.2")
// implementation("org.xerial:sqlite-jdbc:3.48.0.0") // implementation("org.xerial:sqlite-jdbc:3.48.0.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2")

View file

@ -17,6 +17,7 @@ class FilemureApp(repository: SqliteRepository) {
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) private val documentController = DocumentController(modifiers, repository)
private val searchController = SearchController(modifiers, repository)
fun register(server: Javalin) { fun register(server: Javalin) {
server.beforeMatched { ctx -> server.beforeMatched { ctx ->
@ -49,6 +50,8 @@ class FilemureApp(repository: SqliteRepository) {
server.get("/file/{extId}/download", documentController::downloadFile, Role.USER) server.get("/file/{extId}/download", documentController::downloadFile, Role.USER)
server.get("/file/{extId}/delete", documentController::deleteFileAction, Role.USER) server.get("/file/{extId}/delete", documentController::deleteFileAction, Role.USER)
server.get("/search", searchController::search, Role.USER)
server.exception(UnauthorizedResponse::class.java) { e, ctx -> server.exception(UnauthorizedResponse::class.java) { e, ctx ->
ctx.tempolin( ctx.tempolin(
Frame( Frame(

View file

@ -6,7 +6,7 @@ import java.net.URLEncoder
class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, DocumentCreateForm.Modifiers, Overview.Modifiers, class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, DocumentCreateForm.Modifiers, Overview.Modifiers,
FilePreview.Modifiers, DocumentEditForm.Modifiers, FileList.Modifiers, FilePreview.Modifiers, DocumentEditForm.Modifiers, FileList.Modifiers,
net.h34t.filemure.tpl.Document.Modifiers, OverviewDocuments.Modifiers { net.h34t.filemure.tpl.Document.Modifiers, OverviewDocuments.Modifiers, Search.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)

View file

@ -6,7 +6,10 @@ import io.javalin.http.HttpStatus
import io.javalin.http.UnauthorizedResponse import io.javalin.http.UnauthorizedResponse
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.time.LocalDateTime
import java.time.Month import java.time.Month
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
/** /**
* Outputs an HTML page rendered via TempolinTemplate. * Outputs an HTML page rendered via TempolinTemplate.
@ -58,3 +61,8 @@ fun List<Document>.grouped(): Map<Int, Map<Month, List<Document>>> =
values.groupBy { it.referenceDate.month } values.groupBy { it.referenceDate.month }
} }
private val htmlDtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
val formDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")
fun LocalDateTime.formatHuman() = this.format(htmlDtf)
fun LocalDateTime.formatHtmlForm() = this.format(formDtf)

View file

@ -16,7 +16,6 @@ import java.time.format.FormatStyle
class DocumentController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { class DocumentController(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 formDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")
fun documentDetail(ctx: Context) { fun documentDetail(ctx: Context) {
val session = ctx.requireSession() val session = ctx.requireSession()
@ -86,7 +85,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
content = DocumentCreateForm( content = DocumentCreateForm(
modifiers = modifiers, modifiers = modifiers,
title = title, title = title,
referenceDate = formDtf.format(referenceDate), referenceDate = referenceDate.formatHtmlForm(),
tags = { tags.map { TagsBlock(it) } }, tags = { tags.map { TagsBlock(it) } },
description = description, description = description,
fileId = { selectedFiles.map { FileIdBlock(it.extId.value) }.asSequence() }, fileId = { selectedFiles.map { FileIdBlock(it.extId.value) }.asSequence() },

View file

@ -1,21 +1,14 @@
package net.h34t.filemure.controller package net.h34t.filemure.controller
import io.javalin.http.Context import io.javalin.http.Context
import net.h34t.filemure.TemplateModifiers import net.h34t.filemure.*
import net.h34t.filemure.grouped
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.tpl.Frame import net.h34t.filemure.tpl.Frame
import net.h34t.filemure.tpl.Overview import net.h34t.filemure.tpl.Overview
import net.h34t.filemure.tpl.OverviewDocuments import net.h34t.filemure.tpl.OverviewDocuments
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
private val htmlDtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
fun overview(ctx: Context) { fun overview(ctx: Context) {
val session = ctx.requireSession() val session = ctx.requireSession()
@ -71,10 +64,10 @@ class OverviewController(val modifiers: TemplateModifiers, val repository: Sqlit
modifiers = modifiers, modifiers = modifiers,
category = "$year-$month", category = "$year-$month",
document = { document = {
documents.map { document -> documents.sortedBy { it.referenceDate }.map { document ->
DocumentBlock( DocumentBlock(
extId = document.extId.value, extId = document.extId.value,
referenceDate = htmlDtf.format(document.referenceDate), referenceDate = document.referenceDate.formatHtmlForm(),
title = document.title.ifBlank { "untitled" }, title = document.title.ifBlank { "untitled" },
) )
}.asSequence() }.asSequence()

View file

@ -0,0 +1,43 @@
package net.h34t.filemure.controller
import io.javalin.http.Context
import net.h34t.filemure.TemplateModifiers
import net.h34t.filemure.formatHuman
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.Search
class SearchController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
fun search(ctx: Context) {
val session = ctx.requireSession()
val q = ctx.queryParam("q")
val documents = repository.searchDocuments(accountId = session.id, query = "%$q%")
ctx.tempolin(
Frame(
modifiers = modifiers,
title = "Overview",
isTarget = true,
content = Search(
modifiers = modifiers,
search = q ?: "",
document = {
documents.map { d ->
DocumentBlock(
extId = d.extId.value,
title = d.title,
referenceDate = d.referenceDate.formatHuman()
)
}.asSequence()
}
)
)
)
}
}

View file

@ -231,6 +231,24 @@ class SqliteRepository(url: String) {
} }
} }
fun searchDocuments(accountId: Long, state: State = State.ACTIVE, query: String): List<Document> {
return database.databaseQueries.searchDocument(account_id = accountId, state = state, query = query)
.executeAsList()
.map {
Document(
id = it.id,
extId = it.ext_id,
title = it.title,
description = it.description,
tags = it.tags,
created = it.created,
referenceDate = it.reference_date,
state = it.state,
files = emptyList()
)
}
}
fun loadFile(accountId: Long, extId: ExtId): FileContent { fun loadFile(accountId: Long, extId: ExtId): FileContent {
return database return database
.databaseQueries .databaseQueries
@ -251,6 +269,7 @@ class SqliteRepository(url: String) {
fun setDocumentState(accountId: Long, extId: ExtId, state: State) { fun setDocumentState(accountId: Long, extId: ExtId, state: State) {
database.databaseQueries.setDocumentState(account_id = accountId, ext_id = extId, state = state) database.databaseQueries.setDocumentState(account_id = accountId, ext_id = extId, state = state)
} }
fun setFileState(accountId: Long, extId: ExtId, state: State) { fun setFileState(accountId: Long, extId: ExtId, state: State) {
database.databaseQueries.setFileState(account_id = accountId, ext_id = extId, state = state) database.databaseQueries.setFileState(account_id = accountId, ext_id = extId, state = state)
} }

View file

@ -209,3 +209,24 @@ UPDATE file SET state=? WHERE account_id=? AND ext_id=?;
setFilesState: setFilesState:
UPDATE file SET state=? WHERE account_id=? AND ext_id IN ?; UPDATE file SET state=? WHERE account_id=? AND ext_id IN ?;
searchDocument:
SELECT
id,
account_id,
ext_id,
title,
description,
tags,
created,
reference_date,
state
FROM
document
WHERE
account_id = :account_id AND
state = :state AND
(title LIKE :query OR
description LIKE :query AND
tags LIKE :query);

View file

@ -2,6 +2,10 @@
<p>Files in <a href="/limbo">limbo: {$limboFileCount}</a>.</p> <p>Files in <a href="/limbo">limbo: {$limboFileCount}</a>.</p>
<form action="/search" method="get">
<input type="search" name="q"> <input type="submit" value="search">
</form>
<table> <table>
{for $year} {for $year}
<tr> <tr>

View file

@ -0,0 +1,25 @@
<h1>Search for &quot;{*$search}&quot;</h1>
<form action="/search" method="get">
<input type="search" name="q" value="{*$search}"> <input type="submit" value="search">
</form>
<h2>Results</h2>
<table>
<tr>
<th>Date</th>
<th>Title</th>
<th>Details</th>
</tr>
{for $document}
<tr>
<td>{*$referenceDate}</td>
<td>{*$title}</td>
<td><a href="/document/{*$extId}">
<button>details</button>
</a></td>
</tr>
{/for}
</table>