From 3045f46525c8e971375da5d0718bd6c4af299974 Mon Sep 17 00:00:00 2001 From: Stefan Schallerl Date: Thu, 6 Feb 2025 22:54:51 +0100 Subject: [PATCH] Adds document search. --- app/build.gradle.kts | 1 + .../kotlin/net/h34t/filemure/FilemureApp.kt | 3 ++ .../net/h34t/filemure/TemplateModifiers.kt | 2 +- app/src/main/kotlin/net/h34t/filemure/Util.kt | 8 ++++ .../filemure/controller/DocumentController.kt | 3 +- .../filemure/controller/OverviewController.kt | 13 ++---- .../filemure/controller/SearchController.kt | 43 +++++++++++++++++++ .../filemure/repository/SqliteRepository.kt | 19 ++++++++ .../net/h34t/filemure/db/Database.sq | 21 +++++++++ .../net.h34t.filemure.tpl/Overview.tpl.html | 4 ++ .../tpl/net.h34t.filemure.tpl/Search.tpl.html | 25 +++++++++++ 11 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 app/src/main/kotlin/net/h34t/filemure/controller/SearchController.kt create mode 100644 app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 117aa4a..e0fe251 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,6 +10,7 @@ plugins { } dependencies { + implementation("org.apache.pdfbox:pdfbox:3.0.4") implementation("app.cash.sqldelight:sqlite-driver:2.0.2") // implementation("org.xerial:sqlite-jdbc:3.48.0.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2") diff --git a/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt b/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt index ac55b23..eca07f8 100644 --- a/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt +++ b/app/src/main/kotlin/net/h34t/filemure/FilemureApp.kt @@ -17,6 +17,7 @@ class FilemureApp(repository: SqliteRepository) { private val limboController = LimboController(modifiers, repository) private val uploadController = UploadController(modifiers, repository) private val documentController = DocumentController(modifiers, repository) + private val searchController = SearchController(modifiers, repository) fun register(server: Javalin) { server.beforeMatched { ctx -> @@ -49,6 +50,8 @@ class FilemureApp(repository: SqliteRepository) { server.get("/file/{extId}/download", documentController::downloadFile, 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 -> ctx.tempolin( Frame( diff --git a/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt b/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt index e9feb32..edb08c6 100644 --- a/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt +++ b/app/src/main/kotlin/net/h34t/filemure/TemplateModifiers.kt @@ -6,7 +6,7 @@ import java.net.URLEncoder class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, DocumentCreateForm.Modifiers, Overview.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 { 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 254865b..e5a3619 100644 --- a/app/src/main/kotlin/net/h34t/filemure/Util.kt +++ b/app/src/main/kotlin/net/h34t/filemure/Util.kt @@ -6,7 +6,10 @@ import io.javalin.http.HttpStatus import io.javalin.http.UnauthorizedResponse import java.io.BufferedOutputStream import java.io.OutputStreamWriter +import java.time.LocalDateTime import java.time.Month +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle /** * Outputs an HTML page rendered via TempolinTemplate. @@ -58,3 +61,8 @@ fun List.grouped(): Map>> = 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) diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt index 5f7645a..0500f35 100644 --- a/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt +++ b/app/src/main/kotlin/net/h34t/filemure/controller/DocumentController.kt @@ -16,7 +16,6 @@ import java.time.format.FormatStyle class DocumentController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { private val dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT) - private val formDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") fun documentDetail(ctx: Context) { val session = ctx.requireSession() @@ -86,7 +85,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit content = DocumentCreateForm( modifiers = modifiers, title = title, - referenceDate = formDtf.format(referenceDate), + referenceDate = referenceDate.formatHtmlForm(), tags = { tags.map { TagsBlock(it) } }, description = description, fileId = { selectedFiles.map { FileIdBlock(it.extId.value) }.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 985500e..16f5975 100644 --- a/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt +++ b/app/src/main/kotlin/net/h34t/filemure/controller/OverviewController.kt @@ -1,21 +1,14 @@ package net.h34t.filemure.controller import io.javalin.http.Context -import net.h34t.filemure.TemplateModifiers -import net.h34t.filemure.grouped +import net.h34t.filemure.* 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 import net.h34t.filemure.tpl.OverviewDocuments -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) { - private val htmlDtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT) - fun overview(ctx: Context) { val session = ctx.requireSession() @@ -71,10 +64,10 @@ class OverviewController(val modifiers: TemplateModifiers, val repository: Sqlit modifiers = modifiers, category = "$year-$month", document = { - documents.map { document -> + documents.sortedBy { it.referenceDate }.map { document -> DocumentBlock( extId = document.extId.value, - referenceDate = htmlDtf.format(document.referenceDate), + referenceDate = document.referenceDate.formatHtmlForm(), title = document.title.ifBlank { "untitled" }, ) }.asSequence() diff --git a/app/src/main/kotlin/net/h34t/filemure/controller/SearchController.kt b/app/src/main/kotlin/net/h34t/filemure/controller/SearchController.kt new file mode 100644 index 0000000..9225b5e --- /dev/null +++ b/app/src/main/kotlin/net/h34t/filemure/controller/SearchController.kt @@ -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() + } + ) + ) + ) + } + +} \ No newline at end of file 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 203227b..ec89a71 100644 --- a/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt +++ b/app/src/main/kotlin/net/h34t/filemure/repository/SqliteRepository.kt @@ -231,6 +231,24 @@ class SqliteRepository(url: String) { } } + fun searchDocuments(accountId: Long, state: State = State.ACTIVE, query: String): List { + 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 { return database .databaseQueries @@ -251,6 +269,7 @@ class SqliteRepository(url: String) { fun setDocumentState(accountId: Long, extId: ExtId, state: State) { database.databaseQueries.setDocumentState(account_id = accountId, ext_id = extId, state = state) } + fun setFileState(accountId: Long, extId: ExtId, state: State) { database.databaseQueries.setFileState(account_id = accountId, ext_id = extId, state = state) } diff --git a/app/src/main/sqldelight/net/h34t/filemure/db/Database.sq b/app/src/main/sqldelight/net/h34t/filemure/db/Database.sq index 0960fcc..8a1ecb4 100644 --- a/app/src/main/sqldelight/net/h34t/filemure/db/Database.sq +++ b/app/src/main/sqldelight/net/h34t/filemure/db/Database.sq @@ -209,3 +209,24 @@ UPDATE file SET state=? WHERE account_id=? AND ext_id=?; setFilesState: 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); + diff --git a/app/src/main/tpl/net.h34t.filemure.tpl/Overview.tpl.html b/app/src/main/tpl/net.h34t.filemure.tpl/Overview.tpl.html index ed1a4ec..403cd5f 100644 --- a/app/src/main/tpl/net.h34t.filemure.tpl/Overview.tpl.html +++ b/app/src/main/tpl/net.h34t.filemure.tpl/Overview.tpl.html @@ -2,6 +2,10 @@

Files in limbo: {$limboFileCount}.

+
+ +
+ {for $year} diff --git a/app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html b/app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html new file mode 100644 index 0000000..dcb88e4 --- /dev/null +++ b/app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html @@ -0,0 +1,25 @@ +

Search for "{*$search}"

+ + + + + +

Results

+ +
+ + + + + + + {for $document} + + + + + + {/for} +
DateTitleDetails
{*$referenceDate}{*$title} + +