Adds document search.
This commit is contained in:
parent
c44fa80d7f
commit
3045f46525
11 changed files with 129 additions and 13 deletions
|
@ -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")
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() },
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
25
app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html
Normal file
25
app/src/main/tpl/net.h34t.filemure.tpl/Search.tpl.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<h1>Search for "{*$search}"</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>
|
Loading…
Reference in a new issue