Adds tag search.
This commit is contained in:
parent
2c2db1a42e
commit
65239fc0d2
9 changed files with 118 additions and 17 deletions
|
@ -54,6 +54,7 @@ class FilemureApp(repository: SqliteRepository) {
|
||||||
server.post("/file/{extId}/delete", documentController::deleteFileAction, Role.USER)
|
server.post("/file/{extId}/delete", documentController::deleteFileAction, Role.USER)
|
||||||
|
|
||||||
server.get("/search", searchController::search, Role.USER)
|
server.get("/search", searchController::search, Role.USER)
|
||||||
|
server.get("/tags", searchController::tags, Role.USER)
|
||||||
|
|
||||||
server.exception(UnauthorizedResponse::class.java) { e, ctx ->
|
server.exception(UnauthorizedResponse::class.java) { e, ctx ->
|
||||||
ctx.tempolin(
|
ctx.tempolin(
|
||||||
|
|
|
@ -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, Search.Modifiers {
|
net.h34t.filemure.tpl.Document.Modifiers, OverviewDocuments.Modifiers, Search.Modifiers, Tags.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)
|
||||||
|
|
|
@ -38,10 +38,19 @@ data class Document(
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class Tag(val value: String) {
|
value class Tag private constructor(val value: String) {
|
||||||
init {
|
|
||||||
// TODO proper validation
|
companion object {
|
||||||
require(value.isNotBlank())
|
private val validator = Regex("[a-zA-Z0-9]+")
|
||||||
|
private val splitter = Regex("\\s+")
|
||||||
|
fun of(value: String): Tag =
|
||||||
|
value.trim().let { v ->
|
||||||
|
require(v.matches(validator))
|
||||||
|
Tag(v.lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(text: String?) =
|
||||||
|
text?.split(splitter)?.map { of(it) } ?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ private val tagSplitRegex = Regex("\\s+")
|
||||||
|
|
||||||
object TagAdapter {
|
object TagAdapter {
|
||||||
fun parse(ser: String?): List<Tag> {
|
fun parse(ser: String?): List<Tag> {
|
||||||
return ser?.trim()?.let { if (it.isNotBlank()) it.split(tagSplitRegex).map { Tag(it) } else emptyList() }
|
return ser?.trim()?.let { if (it.isNotBlank()) it.split(tagSplitRegex).map { Tag.of(it) } else emptyList() }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ import net.h34t.filemure.tpl.*
|
||||||
import net.h34t.filemure.tpl.Document
|
import net.h34t.filemure.tpl.Document
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.format.FormatStyle
|
|
||||||
|
|
||||||
class DocumentController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
class DocumentController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||||
|
|
||||||
|
@ -117,8 +115,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
||||||
?: throw BadRequestResponse("")
|
?: throw BadRequestResponse("")
|
||||||
val referenceDate = ctx.formParam("reference_date")?.let { LocalDateTime.parse(it, formDtf) }
|
val referenceDate = ctx.formParam("reference_date")?.let { LocalDateTime.parse(it, formDtf) }
|
||||||
?: throw BadRequestResponse("")
|
?: throw BadRequestResponse("")
|
||||||
val tags = ctx.formParam("tags")?.split("\\s+")
|
val tags = Tag.parse(ctx.formParam("tags"))
|
||||||
?: emptyList()
|
|
||||||
val description = ctx.formParam("description")
|
val description = ctx.formParam("description")
|
||||||
?: throw BadRequestResponse("")
|
?: throw BadRequestResponse("")
|
||||||
val fileExtIds = ctx.formParams("file_id")
|
val fileExtIds = ctx.formParams("file_id")
|
||||||
|
@ -128,7 +125,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
||||||
session.id,
|
session.id,
|
||||||
title,
|
title,
|
||||||
referenceDate,
|
referenceDate,
|
||||||
tags.map { Tag(it) },
|
tags,
|
||||||
description,
|
description,
|
||||||
fileExtIds.map { ExtId(it) })
|
fileExtIds.map { ExtId(it) })
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
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.formatHumanShort
|
|
||||||
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.Search
|
import net.h34t.filemure.tpl.Search
|
||||||
|
import net.h34t.filemure.tpl.Tags
|
||||||
|
|
||||||
class SearchController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
class SearchController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||||
|
|
||||||
|
@ -42,4 +40,36 @@ class SearchController(val modifiers: TemplateModifiers, val repository: SqliteR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tags(ctx: Context) {
|
||||||
|
val session = ctx.requireSession()
|
||||||
|
|
||||||
|
val t = ctx.queryParams("t").map { Tag.of(it) }
|
||||||
|
|
||||||
|
val tags = repository.getAllTags(session.id)
|
||||||
|
|
||||||
|
val documents = repository.getDocumentsByTags(accountId = session.id, tags = t)
|
||||||
|
|
||||||
|
ctx.tempolin(
|
||||||
|
Frame(
|
||||||
|
modifiers = modifiers,
|
||||||
|
title = "Search",
|
||||||
|
target = "document",
|
||||||
|
back = "/",
|
||||||
|
logout = true,
|
||||||
|
content = Tags(
|
||||||
|
modifiers = modifiers,
|
||||||
|
tag = { tags.map { TagBlock(tag = it.value) }.asSequence() },
|
||||||
|
document = {
|
||||||
|
documents.map { d ->
|
||||||
|
DocumentBlock(
|
||||||
|
extId = d.extId.value,
|
||||||
|
title = d.title,
|
||||||
|
referenceDate = d.referenceDate.formatHumanShort()
|
||||||
|
)
|
||||||
|
}.asSequence()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -317,7 +317,7 @@ class SqliteRepository(url: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchDocuments(accountId: Long, state: State = State.ACTIVE, query: String): List<Document> {
|
fun searchDocuments(accountId: Long, state: State = State.ACTIVE, query: String): List<Document> {
|
||||||
return database.databaseQueries.searchDocument(account_id = accountId, state = state, query = query)
|
return database.databaseQueries.searchDocuments(account_id = accountId, state = state, query = query)
|
||||||
.executeAsList()
|
.executeAsList()
|
||||||
.map {
|
.map {
|
||||||
Document(
|
Document(
|
||||||
|
@ -359,5 +359,28 @@ class SqliteRepository(url: String) {
|
||||||
database.databaseQueries.setFileState(account_id = accountId, ext_id = extId, state = state)
|
database.databaseQueries.setFileState(account_id = accountId, ext_id = extId, state = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllTags(accountId: Long): List<Tag> =
|
||||||
|
database.databaseQueries.getAllTags(accountId).executeAsList().flatten().distinct()
|
||||||
|
|
||||||
|
|
||||||
private fun lastInsertedId() = database.databaseQueries.getLastInsertRowId().executeAsOne()
|
private fun lastInsertedId() = database.databaseQueries.getLastInsertRowId().executeAsOne()
|
||||||
|
|
||||||
|
fun getDocumentsByTags(accountId: Long, state: State = State.ACTIVE, tags: List<Tag>): List<Document> {
|
||||||
|
return database.databaseQueries.getDocuments(account_id = accountId, state = state)
|
||||||
|
.executeAsList()
|
||||||
|
.filter { d -> (d.tags intersect tags).isNotEmpty() }
|
||||||
|
.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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -219,7 +219,7 @@ 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:
|
searchDocuments:
|
||||||
SELECT
|
SELECT
|
||||||
d.id,
|
d.id,
|
||||||
d.account_id,
|
d.account_id,
|
||||||
|
@ -241,3 +241,5 @@ WHERE
|
||||||
f.filename LIKE :query OR
|
f.filename LIKE :query OR
|
||||||
f.content_extracted LIKE :query);
|
f.content_extracted LIKE :query);
|
||||||
|
|
||||||
|
getAllTags:
|
||||||
|
SELECT tags FROM document WHERE account_id=:accountId;
|
||||||
|
|
39
app/src/main/tpl/net.h34t.filemure.tpl/Tags.tpl.html
Normal file
39
app/src/main/tpl/net.h34t.filemure.tpl/Tags.tpl.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<form action="/tags" method="get">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Tags</legend>
|
||||||
|
<div class="tags">
|
||||||
|
{for $tag}
|
||||||
|
<button class="chip fill round tag">
|
||||||
|
<i>done</i>
|
||||||
|
<span>{*$tag}</span>
|
||||||
|
</button>
|
||||||
|
{/for}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Results</legend>
|
||||||
|
|
||||||
|
<table class="stripes">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Details</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{for $document}
|
||||||
|
<tr>
|
||||||
|
<td>{*$referenceDate}</td>
|
||||||
|
<td>{*$title}</td>
|
||||||
|
<td><a href="/document/{*$extId}">
|
||||||
|
<button>details</button>
|
||||||
|
</a></td>
|
||||||
|
</tr>
|
||||||
|
{/for}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
Loading…
Reference in a new issue