Adds SQLDelight type converters.
This commit is contained in:
parent
b4f2f51032
commit
319c58bcc2
8 changed files with 140 additions and 87 deletions
|
@ -5,7 +5,8 @@ import org.apache.commons.text.StringEscapeUtils
|
|||
import java.net.URLEncoder
|
||||
|
||||
class TemplateModifiers : Frame.Modifiers, Limbo.Modifiers, DocumentCreateForm.Modifiers, Overview.Modifiers,
|
||||
Document.Modifiers, FilePreview.Modifiers, DocumentEditForm.Modifiers, FileList.Modifiers {
|
||||
FilePreview.Modifiers, DocumentEditForm.Modifiers, FileList.Modifiers,
|
||||
net.h34t.filemure.tpl.Document.Modifiers {
|
||||
|
||||
fun hashPrefix(arg: String): String {
|
||||
return URLEncoder.encode(arg, Charsets.UTF_8)
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
package net.h34t.filemure.core.entity
|
||||
package net.h34t.filemure
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
@JvmInline
|
||||
value class ExtId(val value: String) {
|
||||
|
||||
companion object {
|
||||
private val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
|
||||
fun generate(): ExtId {
|
||||
return ExtId((0..8).map { chars.random() }.joinToString(""))
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = value
|
||||
|
||||
}
|
||||
|
||||
data class Document(
|
||||
val id: Long,
|
||||
val extId: String,
|
||||
val extId: ExtId,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val tags: List<Tag>,
|
||||
|
@ -20,19 +36,11 @@ value class Tag(val value: String) {
|
|||
// TODO proper validation
|
||||
require(value.isNotBlank())
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parse(ser: String?): List<Tag> {
|
||||
return ser?.let { if (it.isNotBlank()) it.split(",").map { Tag(it) } else emptyList() } ?: emptyList()
|
||||
}
|
||||
|
||||
fun List<Tag>.serialize() = if (this.isEmpty()) "" else this.joinToString(",") { it.value }
|
||||
}
|
||||
}
|
||||
|
||||
data class FileRef(
|
||||
val id: Long,
|
||||
val extId: String,
|
||||
val extId: ExtId,
|
||||
val accountId: Long,
|
||||
val documentId: Long?,
|
||||
val filename: String,
|
||||
|
@ -45,7 +53,7 @@ data class FileRef(
|
|||
|
||||
data class FileContent(
|
||||
val id: Long,
|
||||
val extId: String,
|
||||
val extId: ExtId,
|
||||
val filename: String,
|
||||
val contentType: String?,
|
||||
val contentExtracted: String?,
|
|
@ -36,20 +36,6 @@ fun Context.setSession(session: Session?) = this.sessionAttribute("session", ses
|
|||
|
||||
fun Context.requireSession(): Session = this.getSession() ?: throw UnauthorizedResponse("Not logged in")
|
||||
|
||||
private val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
|
||||
|
||||
@JvmInline
|
||||
value class ExtId(val value: String) {
|
||||
override fun toString() = value
|
||||
|
||||
companion object {
|
||||
fun generate(): ExtId {
|
||||
return ExtId((0..8).map { chars.random() }.joinToString(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun formatHumanReadableSize(bytes: Long) = when (bytes) {
|
||||
in 0L..<1024L -> "$bytes bytes"
|
||||
in 1025..<1024 * 1_000 -> "${bytes / 1000} kb"
|
||||
|
@ -57,3 +43,10 @@ fun formatHumanReadableSize(bytes: Long) = when (bytes) {
|
|||
else -> "${bytes / 1_000_000_000} gb"
|
||||
}
|
||||
|
||||
object TagAdapter {
|
||||
fun parse(ser: String?): List<Tag> {
|
||||
return ser?.let { if (it.isNotBlank()) it.split(",").map { Tag(it) } else emptyList() } ?: emptyList()
|
||||
}
|
||||
|
||||
fun List<Tag>.serialize() = if (this.isEmpty()) "" else this.joinToString(",") { it.value }
|
||||
}
|
|
@ -5,10 +5,9 @@ import io.javalin.http.Context
|
|||
import io.javalin.http.ForbiddenResponse
|
||||
import io.javalin.http.Header
|
||||
import net.h34t.filemure.*
|
||||
import net.h34t.filemure.core.entity.State
|
||||
import net.h34t.filemure.core.entity.Tag
|
||||
import net.h34t.filemure.repository.SqliteRepository
|
||||
import net.h34t.filemure.tpl.*
|
||||
import net.h34t.filemure.tpl.Document
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
@ -32,7 +31,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
isTarget = true,
|
||||
content = Document(
|
||||
modifiers = modifiers,
|
||||
extId = document.extId,
|
||||
extId = document.extId.value,
|
||||
title = document.title,
|
||||
referenceDate = dtf.format(document.referenceDate),
|
||||
tags = { document.tags.map { TagsBlock(tag = it.value) }.asSequence() },
|
||||
|
@ -43,7 +42,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
files = {
|
||||
document.files.map { file ->
|
||||
FilesBlock(
|
||||
extId = file.extId,
|
||||
extId = file.extId.value,
|
||||
filename = file.filename,
|
||||
contentType = file.contentType ?: "?",
|
||||
size = formatHumanReadableSize(file.fileSize),
|
||||
|
@ -60,7 +59,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
fun createDocumentForm(ctx: Context) {
|
||||
val session = ctx.requireSession()
|
||||
|
||||
val fileIds = ctx.queryParams("file_id")
|
||||
val fileIds = ctx.queryParams("file_id").map { ExtId(it) }
|
||||
|
||||
val limboFiles = repository.getFilesInLimbo(session.id)
|
||||
val limboFileIds = limboFiles.map { it.extId }
|
||||
|
@ -96,7 +95,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
files = {
|
||||
selectedFiles.map { file ->
|
||||
FilesBlock(
|
||||
extId = file.extId,
|
||||
extId = file.extId.value,
|
||||
filename = file.filename,
|
||||
contentType = file.contentType ?: "?",
|
||||
size = formatHumanReadableSize(file.fileSize),
|
||||
|
@ -174,7 +173,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
files = {
|
||||
document.files.map { file ->
|
||||
FilesBlock(
|
||||
extId = file.extId,
|
||||
extId = file.extId.value,
|
||||
filename = file.filename,
|
||||
contentType = file.contentType ?: "?",
|
||||
size = formatHumanReadableSize(file.fileSize),
|
||||
|
@ -207,7 +206,7 @@ class DocumentController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
id = document.id,
|
||||
title = title ?: "",
|
||||
referenceDate = LocalDateTime.parse(referenceDate ?: "", formDtf),
|
||||
tags = Tag.parse(tags),
|
||||
tags = TagAdapter.parse(tags),
|
||||
description = description
|
||||
)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class LimboController(val modifiers: TemplateModifiers, val repository: SqliteRe
|
|||
modifiers = modifiers, limboFileCount = files.size.toString(), file = {
|
||||
files.map { f ->
|
||||
FileBlock(
|
||||
extId = f.extId,
|
||||
extId = f.extId.value,
|
||||
file = f.filename,
|
||||
type = f.contentType ?: "",
|
||||
size = f.fileSize.toString(),
|
||||
|
|
|
@ -12,7 +12,7 @@ import java.time.format.FormatStyle
|
|||
|
||||
class OverviewController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
|
||||
|
||||
val dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
|
||||
private val htmlDtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
|
||||
|
||||
fun overview(ctx: Context) {
|
||||
val session = ctx.requireSession()
|
||||
|
@ -24,7 +24,7 @@ class OverviewController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
ctx.tempolin(
|
||||
Frame(
|
||||
modifiers = modifiers,
|
||||
title = "Filemure Overview",
|
||||
title = "Overview",
|
||||
isTarget = true,
|
||||
content = Overview(
|
||||
modifiers = modifiers,
|
||||
|
@ -32,8 +32,8 @@ class OverviewController(val modifiers: TemplateModifiers, val repository: Sqlit
|
|||
document = {
|
||||
documents.map { document ->
|
||||
DocumentBlock(
|
||||
extId = document.extId,
|
||||
referenceDate = dtf.format(document.referenceDate),
|
||||
extId = document.extId.value,
|
||||
referenceDate = htmlDtf.format(document.referenceDate),
|
||||
title = document.title.ifBlank { "untitled" },
|
||||
)
|
||||
}.asSequence()
|
||||
|
|
|
@ -1,34 +1,78 @@
|
|||
package net.h34t.filemure.repository
|
||||
|
||||
import app.cash.sqldelight.ColumnAdapter
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
import net.h34t.filemure.ExtId
|
||||
import net.h34t.filemure.core.entity.*
|
||||
import net.h34t.filemure.core.entity.Tag.Companion.serialize
|
||||
import net.h34t.filemure.*
|
||||
import net.h34t.filemure.TagAdapter.serialize
|
||||
import net.h34t.filemure.db.Database
|
||||
import net.h34t.filemure.db.File_
|
||||
import java.io.InputStream
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class SqliteRepository(url: String) {
|
||||
|
||||
private val sqliteDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
private companion object Adapters {
|
||||
private val sqliteDtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
val localDateTimeAdapter = object : ColumnAdapter<LocalDateTime, String> {
|
||||
override fun decode(databaseValue: String): LocalDateTime {
|
||||
return LocalDateTime.parse(databaseValue, sqliteDtf)
|
||||
}
|
||||
|
||||
override fun encode(value: LocalDateTime): String {
|
||||
return value.format(sqliteDtf)
|
||||
}
|
||||
}
|
||||
|
||||
val stateAdapter = object : ColumnAdapter<State, Long> {
|
||||
override fun decode(databaseValue: Long): State = State.fromCode(databaseValue.toInt())
|
||||
override fun encode(value: State): Long = value.code.toLong()
|
||||
}
|
||||
|
||||
val tagsAdapter = object : ColumnAdapter<List<Tag>, String> {
|
||||
override fun decode(databaseValue: String): List<Tag> = TagAdapter.parse(databaseValue)
|
||||
override fun encode(value: List<Tag>): String = value.serialize()
|
||||
}
|
||||
|
||||
val extIdAdapter = object : ColumnAdapter<ExtId, String> {
|
||||
override fun decode(databaseValue: String): ExtId = ExtId(databaseValue)
|
||||
override fun encode(value: ExtId): String = value.value
|
||||
}
|
||||
}
|
||||
|
||||
// private val connection: Connection = DriverManager.getConnection(url)
|
||||
private val database: Database
|
||||
|
||||
init {
|
||||
val driver: SqlDriver = JdbcSqliteDriver("jdbc:sqlite:test.db")
|
||||
Database.Schema.create(driver)
|
||||
database = Database(driver)
|
||||
}
|
||||
val driver: SqlDriver = JdbcSqliteDriver(url)
|
||||
|
||||
private fun toLDT(value: String) = LocalDateTime.parse(value, sqliteDtf)
|
||||
try {
|
||||
Database.Schema.create(driver)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
database = Database(
|
||||
driver = driver,
|
||||
documentAdapter = net.h34t.filemure.db.Document.Adapter(
|
||||
tagsAdapter = tagsAdapter,
|
||||
createdAdapter = localDateTimeAdapter,
|
||||
reference_dateAdapter = localDateTimeAdapter,
|
||||
stateAdapter = stateAdapter,
|
||||
ext_idAdapter = extIdAdapter
|
||||
),
|
||||
file_Adapter = File_.Adapter(
|
||||
createdAdapter = localDateTimeAdapter,
|
||||
stateAdapter = stateAdapter,
|
||||
ext_idAdapter = extIdAdapter
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun addFileToLimbo(accountId: Long, filename: String, contentType: String?, size: Long, content: InputStream) {
|
||||
database.databaseQueries.insertFileIntoLimbo(
|
||||
account_id = accountId,
|
||||
ext_id = ExtId.generate().value,
|
||||
ext_id = ExtId.generate(),
|
||||
filename = filename,
|
||||
content_type = contentType,
|
||||
file_size = size,
|
||||
|
@ -37,12 +81,12 @@ class SqliteRepository(url: String) {
|
|||
}
|
||||
|
||||
fun getLimboFileCount(accountId: Long, state: State = State.ACTIVE): Long {
|
||||
return database.databaseQueries.getLimboFileCount(account_id = accountId, state = state.code.toLong())
|
||||
return database.databaseQueries.getLimboFileCount(account_id = accountId, state = state)
|
||||
.executeAsOne()
|
||||
}
|
||||
|
||||
fun getFilesInLimbo(accountId: Long, state: State = State.ACTIVE): List<FileRef> {
|
||||
return database.databaseQueries.getFilesInLimbo(account_id = accountId, state = state.code.toLong())
|
||||
return database.databaseQueries.getFilesInLimbo(account_id = accountId, state = state)
|
||||
.executeAsList()
|
||||
.map {
|
||||
FileRef(
|
||||
|
@ -54,8 +98,8 @@ class SqliteRepository(url: String) {
|
|||
fileSize = it.file_size,
|
||||
contentType = it.content_type,
|
||||
contentExtracted = it.content_extracted,
|
||||
created = toLDT(it.created),
|
||||
state = State.fromCode(it.state.toInt())
|
||||
created = it.created,
|
||||
state = it.state
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -72,11 +116,11 @@ class SqliteRepository(url: String) {
|
|||
database.databaseQueries.transaction {
|
||||
database.databaseQueries.addDocument(
|
||||
account_id = accountId,
|
||||
ext_id = extId.value,
|
||||
ext_id = extId,
|
||||
title = title,
|
||||
description = description,
|
||||
tags = tags.serialize(),
|
||||
reference_date = referenceDate.format(sqliteDtf)
|
||||
tags = tags,
|
||||
reference_date = referenceDate
|
||||
)
|
||||
|
||||
val documentId = database.databaseQueries.getLastInsertRowId().executeAsOne()
|
||||
|
@ -84,7 +128,7 @@ class SqliteRepository(url: String) {
|
|||
database.databaseQueries.attachLimboFilesToDocument(
|
||||
account_id = accountId,
|
||||
document_id = documentId,
|
||||
ext_id = fileExtIds.map { it.value }
|
||||
ext_id = fileExtIds
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -92,7 +136,7 @@ class SqliteRepository(url: String) {
|
|||
}
|
||||
|
||||
fun getDocuments(accountId: Long, state: State = State.ACTIVE): List<Document> {
|
||||
return database.databaseQueries.getDocuments(account_id = accountId, state = state.code.toLong())
|
||||
return database.databaseQueries.getDocuments(account_id = accountId, state = state)
|
||||
.executeAsList()
|
||||
.map {
|
||||
Document(
|
||||
|
@ -100,10 +144,10 @@ class SqliteRepository(url: String) {
|
|||
extId = it.ext_id,
|
||||
title = it.title,
|
||||
description = it.description,
|
||||
tags = Tag.parse(it.tags),
|
||||
created = toLDT(it.created),
|
||||
referenceDate = toLDT(it.reference_date),
|
||||
state = State.fromCode(it.state.toInt()),
|
||||
tags = it.tags,
|
||||
created = it.created,
|
||||
referenceDate = it.reference_date,
|
||||
state = it.state,
|
||||
files = emptyList()
|
||||
)
|
||||
}
|
||||
|
@ -121,10 +165,10 @@ class SqliteRepository(url: String) {
|
|||
database.databaseQueries.updateDocument(
|
||||
id = id,
|
||||
title = title,
|
||||
reference_date = referenceDate.format(sqliteDtf),
|
||||
tags = tags.serialize(),
|
||||
reference_date = referenceDate,
|
||||
tags = tags,
|
||||
description = description,
|
||||
state = state.code.toLong(),
|
||||
state = state,
|
||||
account_id = accountId,
|
||||
)
|
||||
}
|
||||
|
@ -132,18 +176,18 @@ class SqliteRepository(url: String) {
|
|||
fun getDocumentByExtId(accountId: Long, extId: ExtId, state: State): Document {
|
||||
return database.databaseQueries.getDocumentByExtId(
|
||||
account_id = accountId,
|
||||
ext_id = extId.value,
|
||||
state = state.code.toLong()
|
||||
ext_id = extId,
|
||||
state = state
|
||||
).executeAsOne().let { d ->
|
||||
Document(
|
||||
id = d.id,
|
||||
extId = d.ext_id,
|
||||
title = d.title,
|
||||
description = d.description,
|
||||
tags = Tag.parse(d.tags),
|
||||
created = toLDT(d.created),
|
||||
referenceDate = toLDT(d.reference_date),
|
||||
state = State.fromCode(d.state.toInt()),
|
||||
tags = d.tags,
|
||||
created = d.created,
|
||||
referenceDate = d.reference_date,
|
||||
state = d.state,
|
||||
files = database.databaseQueries.getFilesForDocument(
|
||||
document_id = d.id,
|
||||
account_id = accountId
|
||||
|
@ -157,8 +201,8 @@ class SqliteRepository(url: String) {
|
|||
contentType = f.content_type,
|
||||
contentExtracted = f.content_extracted,
|
||||
fileSize = f.file_size,
|
||||
created = toLDT(f.created),
|
||||
state = State.fromCode(f.state.toInt())
|
||||
created = f.created,
|
||||
state = f.state
|
||||
)
|
||||
}
|
||||
)
|
||||
|
@ -169,7 +213,7 @@ class SqliteRepository(url: String) {
|
|||
fun loadFile(accountId: Long, extId: ExtId): FileContent {
|
||||
return database
|
||||
.databaseQueries
|
||||
.getFile(account_id = accountId, ext_id = extId.value)
|
||||
.getFile(account_id = accountId, ext_id = extId)
|
||||
.executeAsOne().let { f ->
|
||||
FileContent(
|
||||
id = f.id,
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
import java.time.LocalDateTime;
|
||||
import kotlin.collections.List;
|
||||
import net.h34t.filemure.ExtId;
|
||||
import net.h34t.filemure.State;
|
||||
import net.h34t.filemure.Tag;
|
||||
|
||||
-- account definition
|
||||
|
||||
CREATE TABLE account (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
ext_id TEXT AS ExtId NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created TEXT DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
state INTEGER DEFAULT (1) NOT NULL
|
||||
created TEXT AS LocalDateTime DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
state INTEGER AS State DEFAULT (1) NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX account_state_IDX ON account (state);
|
||||
CREATE UNIQUE INDEX account_email_IDX ON account (email);
|
||||
CREATE UNIQUE INDEX account_extid_IDX ON account (ext_id);
|
||||
|
||||
-- document definition
|
||||
|
||||
CREATE TABLE document (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER NOT NULL,
|
||||
ext_id TEXT NOT NULL,
|
||||
ext_id TEXT AS ExtId NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
tags TEXT NOT NULL,
|
||||
created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
reference_date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
state INTEGER NOT NULL DEFAULT (1),
|
||||
tags TEXT AS List<Tag> NOT NULL,
|
||||
created TEXT AS LocalDateTime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
reference_date TEXT AS LocalDateTime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
state INTEGER AS State NOT NULL DEFAULT (1),
|
||||
CONSTRAINT document_account_FK FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
|
@ -36,14 +44,14 @@ CREATE TABLE file (
|
|||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER NOT NULL,
|
||||
document_id INTEGER DEFAULT NULL,
|
||||
ext_id TEXT NOT NULL,
|
||||
ext_id TEXT AS ExtId NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
file_size INTEGER NOT NULL,
|
||||
content BLOB NOT NULL,
|
||||
content_type TEXT,
|
||||
content_extracted TEXT,
|
||||
created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
state INTEGER NOT NULL DEFAULT (1),
|
||||
created TEXT AS LocalDateTime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
state INTEGER AS State NOT NULL DEFAULT (1),
|
||||
CONSTRAINT file_account_FK FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE,
|
||||
CONSTRAINT file_document_FK FOREIGN KEY (document_id) REFERENCES document(id) ON DELETE CASCADE
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue