Update config

This commit is contained in:
Hubert Van De Walle 2021-04-12 21:13:49 +02:00
parent 204ae7988e
commit 7ad8b7039b
21 changed files with 138 additions and 217 deletions

View File

@ -1,6 +1 @@
# mariadb
MYSQL_ROOT_PASSWORD=
MYSQL_PASSWORD=
# simplenotes
DB_PASSWORD=
JWT_SECRET= JWT_SECRET=

View File

@ -12,18 +12,15 @@ FROM alpine
RUN apk add --no-cache curl RUN apk add --no-cache curl
ENV APPLICATION_USER simplenotes
RUN adduser -D -g '' $APPLICATION_USER
RUN mkdir /app RUN mkdir /app
RUN chown -R $APPLICATION_USER /app RUN mkdir /app/data
USER $APPLICATION_USER
COPY --from=jdkbuilder /myjdk /myjdk COPY --from=jdkbuilder /myjdk /myjdk
COPY app/build/libs/app-with-dependencies*.jar /app/simplenotes.jar COPY app/build/libs/app-with-dependencies*.jar /app/simplenotes.jar
WORKDIR /app WORKDIR /app
VOLUME /app/data
ENV SERVER_HOST 0.0.0.0 ENV SERVER_HOST 0.0.0.0
CMD [ \ CMD [ \

View File

@ -4,7 +4,11 @@ import io.micronaut.context.ApplicationContext
import java.lang.Runtime.getRuntime import java.lang.Runtime.getRuntime
fun main() { fun main() {
val ctx = ApplicationContext.run() val env = if (System.getenv("ENV") == "dev") "dev" else "prod"
val ctx = ApplicationContext.builder()
.deduceEnvironment(false)
.environments(env)
.start()
ctx.createBean(Server::class.java) ctx.createBean(Server::class.java)
getRuntime().addShutdownHook(Thread { ctx.stop() }) getRuntime().addShutdownHook(Thread { ctx.stop() })
} }

View File

@ -1,14 +0,0 @@
package be.simplenotes.app.controllers
import be.simplenotes.domain.HealthCheckService
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
import javax.inject.Singleton
@Singleton
class HealthCheckController(private val healthCheckService: HealthCheckService) {
fun healthCheck(@Suppress("UNUSED_PARAMETER") request: Request) =
if (healthCheckService.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
}

View File

@ -1,7 +1,6 @@
package be.simplenotes.app.routes package be.simplenotes.app.routes
import be.simplenotes.app.controllers.BaseController import be.simplenotes.app.controllers.BaseController
import be.simplenotes.app.controllers.HealthCheckController
import be.simplenotes.app.controllers.NoteController import be.simplenotes.app.controllers.NoteController
import be.simplenotes.app.controllers.UserController import be.simplenotes.app.controllers.UserController
import be.simplenotes.app.filters.ImmutableFilter import be.simplenotes.app.filters.ImmutableFilter
@ -19,7 +18,6 @@ import javax.inject.Singleton
@Singleton @Singleton
class BasicRoutes( class BasicRoutes(
private val healthCheckController: HealthCheckController,
private val baseCtrl: BaseController, private val baseCtrl: BaseController,
private val userCtrl: UserController, private val userCtrl: UserController,
private val noteCtrl: NoteController, private val noteCtrl: NoteController,
@ -52,8 +50,6 @@ class BasicRoutes(
"/notes/public/{uuid}" bind GET to noteCtrl::public, "/notes/public/{uuid}" bind GET to noteCtrl::public,
) )
), ),
"/health" bind GET to healthCheckController::healthCheck,
staticHandler staticHandler
) )
} }

View File

@ -1,10 +1,3 @@
db:
jdbc-url: jdbc:h2:./notes-db;
username: h2
password: ''
connection-timeout: 3000
maximum-pool-size: 10
jwt: jwt:
secret: 'PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms=' secret: 'PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms='
validity: 24 validity: 24
@ -13,3 +6,5 @@ jwt:
server: server:
host: localhost host: localhost
port: 8080 port: 8080
data-dir: ./data

View File

@ -1,32 +0,0 @@
package be.simplenotes.config
import io.micronaut.context.annotation.ConfigurationInject
import io.micronaut.context.annotation.ConfigurationProperties
import java.util.concurrent.TimeUnit
@ConfigurationProperties("db")
data class DataSourceConfig @ConfigurationInject constructor(
val jdbcUrl: String,
val username: String,
val password: String,
val maximumPoolSize: Int,
val connectionTimeout: Long,
) {
override fun toString() = "DataSourceConfig(jdbcUrl='$jdbcUrl', username='$username', password='***', " +
"maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)"
}
@ConfigurationProperties("jwt")
data class JwtConfig @ConfigurationInject constructor(
val secret: String,
val validity: Long,
val timeUnit: TimeUnit,
) {
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
}
@ConfigurationProperties("server")
data class ServerConfig @ConfigurationInject constructor(
val host: String,
val port: Int,
)

58
config/src/DataConfig.kt Normal file
View File

@ -0,0 +1,58 @@
package be.simplenotes.config
import io.micronaut.context.annotation.*
import java.nio.file.Path
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
data class DataConfig(val dataDir: String)
data class DataSourceConfig(
val jdbcUrl: String,
val maximumPoolSize: Int,
val connectionTimeout: Long,
)
@ConfigurationProperties("jwt")
data class JwtConfig @ConfigurationInject constructor(
val secret: String,
val validity: Long,
val timeUnit: TimeUnit,
) {
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
}
@ConfigurationProperties("server")
data class ServerConfig @ConfigurationInject constructor(
val host: String,
val port: Int,
)
@Factory
class ConfigFactory {
@Singleton
@Requires(notEnv = ["test"])
fun datasourceConfig(dataConfig: DataConfig) = DataSourceConfig(
jdbcUrl = "jdbc:h2:" + Path.of(dataConfig.dataDir, "simplenotes").normalize().toAbsolutePath(),
maximumPoolSize = 10,
connectionTimeout = 1000
)
@Singleton
@Requires(env = ["test"])
fun testDatasourceConfig() = DataSourceConfig(
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1",
maximumPoolSize = 2,
connectionTimeout = 1000
)
@Singleton
@Requires(notEnv = ["test"])
fun dataConfig(@Property(name = "data-dir") dataDir: String) = DataConfig(dataDir)
@Singleton
@Requires(env = ["test"])
fun testDataConfig() = DataConfig("/tmp")
}

View File

@ -1,54 +1,19 @@
version: '2.2' version: '2.2'
services: services:
db:
image: mariadb:10.5.5
container_name: simplenotes-mariadb
env_file:
- .env
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Brussels
- MYSQL_DATABASE=simplenotes
- MYSQL_USER=simplenotes
# .env:
# - MYSQL_ROOT_PASSWORD
# - MYSQL_PASSWORD
volumes:
- notes-db-volume:/var/lib/mysql
healthcheck:
test: "mysql --protocol=tcp -u simplenotes -p$MYSQL_PASSWORD -e 'show databases'"
interval: 5s
timeout: 1s
start_period: 2s
retries: 10
simplenotes: simplenotes:
image: hubv/simplenotes image: hubv/simplenotes
container_name: simplenotes container_name: simplenotes
env_file: env_file:
- .env - .env
environment: environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Brussels - TZ=Europe/Brussels
- DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
- DB_USERNAME=simplenotes
# .env: # .env:
# - JWT_SECRET # - JWT_SECRET
# - DB_PASSWORD
ports: ports:
- 127.0.0.1:8080:8080 - 127.0.0.1:8080:8080
healthcheck: volumes:
test: "curl --fail -s http://localhost:8080/health" - ./simplenotes-data:/app/data
interval: 5s
timeout: 1s
start_period: 2s
retries: 3
depends_on:
db:
condition: service_healthy
volumes:
notes-db-volume:

View File

@ -1,13 +0,0 @@
package be.simplenotes.domain
import be.simplenotes.persistence.DbHealthCheck
import javax.inject.Singleton
interface HealthCheckService {
fun isOk(): Boolean
}
@Singleton
class HealthCheckServiceImpl(private val dbHealthCheck: DbHealthCheck) : HealthCheckService {
override fun isOk() = dbHealthCheck.isOk()
}

View File

@ -5,7 +5,6 @@ import be.simplenotes.domain.security.HtmlSanitizer
import be.simplenotes.domain.utils.parseSearchTerms import be.simplenotes.domain.utils.parseSearchTerms
import be.simplenotes.persistence.repositories.NoteRepository import be.simplenotes.persistence.repositories.NoteRepository
import be.simplenotes.persistence.repositories.UserRepository import be.simplenotes.persistence.repositories.UserRepository
import be.simplenotes.persistence.transactions.TransactionService
import be.simplenotes.search.NoteSearcher import be.simplenotes.search.NoteSearcher
import be.simplenotes.types.LoggedInUser import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.Note import be.simplenotes.types.Note
@ -22,31 +21,34 @@ class NoteService(
private val noteRepository: NoteRepository, private val noteRepository: NoteRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val searcher: NoteSearcher, private val searcher: NoteSearcher,
private val htmlSanitizer: HtmlSanitizer, private val htmlSanitizer: HtmlSanitizer
private val transaction: TransactionService,
) { ) {
fun create(user: LoggedInUser, markdownText: String) = transaction.use { fun create(user: LoggedInUser, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
either.eager<MarkdownParsingError, PersistedNote> { markdownService.renderDocument(markdownText)
markdownService.renderDocument(markdownText) .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } .map { Note(title = it.metadata.title, tags = it.metadata.tags, markdown = markdownText, html = it.html) }
.map { Note(it.metadata, markdown = markdownText, html = it.html) } .map { noteRepository.create(user.userId, it) }
.map { noteRepository.create(user.userId, it) } .bind()
.bind() .also { searcher.indexNote(user.userId, it) }
.also { searcher.indexNote(user.userId, it) }
}
} }
fun update(user: LoggedInUser, uuid: UUID, markdownText: String) = transaction.use { fun update(user: LoggedInUser, uuid: UUID, markdownText: String) =
either.eager<MarkdownParsingError, PersistedNote?> { either.eager<MarkdownParsingError, PersistedNote?> {
markdownService.renderDocument(markdownText) markdownService.renderDocument(markdownText)
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { Note(it.metadata, markdown = markdownText, html = it.html) } .map {
Note(
title = it.metadata.title,
tags = it.metadata.tags,
markdown = markdownText,
html = it.html
)
}
.map { noteRepository.update(user.userId, uuid, it) } .map { noteRepository.update(user.userId, uuid, it) }
.bind() .bind()
?.also { searcher.updateIndex(user.userId, it) } ?.also { searcher.updateIndex(user.userId, it) }
} }
}
fun paginatedNotes( fun paginatedNotes(
userId: Int, userId: Int,
@ -64,22 +66,22 @@ class NoteService(
fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid) fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid)
fun trash(userId: Int, uuid: UUID): Boolean = transaction.use { fun trash(userId: Int, uuid: UUID): Boolean {
val res = noteRepository.delete(userId, uuid, permanent = false) val res = noteRepository.delete(userId, uuid, permanent = false)
if (res) searcher.deleteIndex(userId, uuid) if (res) searcher.deleteIndex(userId, uuid)
res return res
} }
fun restore(userId: Int, uuid: UUID): Boolean = transaction.use { fun restore(userId: Int, uuid: UUID): Boolean {
val res = noteRepository.restore(userId, uuid) val res = noteRepository.restore(userId, uuid)
if (res) find(userId, uuid)?.let { note -> searcher.indexNote(userId, note) } if (res) find(userId, uuid)?.let { note -> searcher.indexNote(userId, note) }
res return res
} }
fun delete(userId: Int, uuid: UUID): Boolean = transaction.use { fun delete(userId: Int, uuid: UUID): Boolean {
val res = noteRepository.delete(userId, uuid, permanent = true) val res = noteRepository.delete(userId, uuid, permanent = true)
if (res) searcher.deleteIndex(userId, uuid) if (res) searcher.deleteIndex(userId, uuid)
res return res
} }
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true) fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
@ -99,8 +101,8 @@ class NoteService(
@PreDestroy @PreDestroy
fun dropAllIndexes() = searcher.dropAll() fun dropAllIndexes() = searcher.dropAll()
fun makePublic(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePublic(userId, uuid) } fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)
fun makePrivate(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePrivate(userId, uuid) } fun makePrivate(userId: Int, uuid: UUID) = noteRepository.makePrivate(userId, uuid)
fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid) fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid)
} }

View File

@ -9,7 +9,6 @@ import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.security.SimpleJwt import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.domain.validation.UserValidations import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistence.repositories.UserRepository import be.simplenotes.persistence.repositories.UserRepository
import be.simplenotes.persistence.transactions.TransactionService
import be.simplenotes.search.NoteSearcher import be.simplenotes.search.NoteSearcher
import be.simplenotes.types.LoggedInUser import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.PersistedUser import be.simplenotes.types.PersistedUser
@ -29,16 +28,13 @@ internal class UserServiceImpl(
private val passwordHash: PasswordHash, private val passwordHash: PasswordHash,
private val jwt: SimpleJwt<LoggedInUser>, private val jwt: SimpleJwt<LoggedInUser>,
private val searcher: NoteSearcher, private val searcher: NoteSearcher,
private val transactionService: TransactionService,
) : UserService { ) : UserService {
override fun register(form: RegisterForm) = transactionService.use { override fun register(form: RegisterForm) = UserValidations.validateRegister(form)
UserValidations.validateRegister(form) .filterOrElse({ !userRepository.exists(it.username) }, { RegisterError.UserExists })
.filterOrElse({ !userRepository.exists(it.username) }, { RegisterError.UserExists }) .map { it.copy(password = passwordHash.crypt(it.password)) }
.map { it.copy(password = passwordHash.crypt(it.password)) } .map { userRepository.create(it) }
.map { userRepository.create(it) } .leftIfNull { RegisterError.UserExists }
.leftIfNull { RegisterError.UserExists }
}
override fun login(form: LoginForm) = either.eager<LoginError, Token> { override fun login(form: LoginForm) = either.eager<LoginError, Token> {
UserValidations.validateLogin(form) UserValidations.validateLogin(form)
@ -50,18 +46,16 @@ internal class UserServiceImpl(
.bind() .bind()
} }
override fun delete(form: DeleteForm) = transactionService.use { override fun delete(form: DeleteForm) = either.eager<DeleteError, Unit> {
either.eager<DeleteError, Unit> { val user = !UserValidations.validateDelete(form)
val user = !UserValidations.validateDelete(form) val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered }
val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered } !Either.conditionally(
!Either.conditionally( passwordHash.verify(user.password, persistedUser.password),
passwordHash.verify(user.password, persistedUser.password), { DeleteError.WrongPassword },
{ DeleteError.WrongPassword }, { }
{ } )
) !Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { })
!Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { }) searcher.dropIndex(persistedUser.id)
searcher.dropIndex(persistedUser.id)
}
} }
} }

View File

@ -9,7 +9,6 @@ import be.simplenotes.domain.security.UserJwtMapper
import be.simplenotes.domain.testutils.isLeftOfType import be.simplenotes.domain.testutils.isLeftOfType
import be.simplenotes.domain.testutils.isRight import be.simplenotes.domain.testutils.isRight
import be.simplenotes.persistence.repositories.UserRepository import be.simplenotes.persistence.repositories.UserRepository
import be.simplenotes.persistence.transactions.TransactionService
import be.simplenotes.types.PersistedUser import be.simplenotes.types.PersistedUser
import com.natpryce.hamkrest.assertion.assertThat import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo import com.natpryce.hamkrest.equalTo
@ -23,16 +22,12 @@ internal class UserServiceTest {
val passwordHash = BcryptPasswordHash(test = true) val passwordHash = BcryptPasswordHash(test = true)
val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS) val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS)
val simpleJwt = SimpleJwt(jwtConfig, UserJwtMapper()) val simpleJwt = SimpleJwt(jwtConfig, UserJwtMapper())
val noopTransactionService = object : TransactionService {
override fun <T> use(block: () -> T) = block()
}
val userService = UserServiceImpl( val userService = UserServiceImpl(
userRepository = userRepository, userRepository = userRepository,
passwordHash = passwordHash, passwordHash = passwordHash,
jwt = simpleJwt, jwt = simpleJwt,
searcher = mockk(), searcher = mockk(),
transactionService = noopTransactionService
) )
@BeforeEach @BeforeEach

View File

@ -29,8 +29,8 @@ class PersistenceModule {
val hikariConfig = HikariConfig().also { val hikariConfig = HikariConfig().also {
it.jdbcUrl = conf.jdbcUrl it.jdbcUrl = conf.jdbcUrl
it.driverClassName = "org.h2.Driver" it.driverClassName = "org.h2.Driver"
it.username = conf.username it.username = ""
it.password = conf.password it.password = ""
it.maximumPoolSize = conf.maximumPoolSize it.maximumPoolSize = conf.maximumPoolSize
it.connectionTimeout = conf.connectionTimeout it.connectionTimeout = conf.connectionTimeout
it.dataSourceProperties["CASE_INSENSITIVE_IDENTIFIERS"] = "TRUE" it.dataSourceProperties["CASE_INSENSITIVE_IDENTIFIERS"] = "TRUE"

View File

@ -1,10 +1,9 @@
package be.simplenotes.persistence package be.simplenotes.persistence
import be.simplenotes.config.DataSourceConfig import io.micronaut.context.ApplicationContext
import io.micronaut.context.BeanContext import io.micronaut.context.BeanContext
import org.flywaydb.core.Flyway import org.flywaydb.core.Flyway
import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.parallel.ResourceLock import org.junit.jupiter.api.parallel.ResourceLock
import javax.sql.DataSource import javax.sql.DataSource
@ -12,25 +11,10 @@ import javax.sql.DataSource
@ResourceLock("h2") @ResourceLock("h2")
abstract class DbTest { abstract class DbTest {
val beanContext = BeanContext.build() val beanContext = ApplicationContext.build().deduceEnvironment(false).environments("test").start()
inline fun <reified T> BeanContext.getBean(): T = getBean(T::class.java) inline fun <reified T> BeanContext.getBean(): T = getBean(T::class.java)
@BeforeAll
fun setComponent() {
beanContext
.registerSingleton(
DataSourceConfig(
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1",
username = "h2",
password = "",
maximumPoolSize = 2,
connectionTimeout = 3000
)
)
.start()
}
@BeforeEach @BeforeEach
fun beforeEach() { fun beforeEach() {
val migration = beanContext.getBean<DbMigrations>() val migration = beanContext.getBean<DbMigrations>()

View File

@ -8,6 +8,7 @@ plugins {
dependencies { dependencies {
implementation(project(":types")) implementation(project(":types"))
implementation(project(":config"))
implementation(Libs.Lucene.core) implementation(Libs.Lucene.core)
implementation(Libs.Lucene.queryParser) implementation(Libs.Lucene.queryParser)

View File

@ -7,18 +7,15 @@ import org.apache.lucene.document.Field
import org.apache.lucene.document.StringField import org.apache.lucene.document.StringField
import org.apache.lucene.document.TextField import org.apache.lucene.document.TextField
internal fun PersistedNote.toDocument(): Document { internal fun PersistedNote.toDocument() = Document().apply {
val note = this // non searchable fields
return Document().apply { add(StringField(uuidField, UuidFieldConverter.toDoc(uuid), Field.Store.YES))
// non searchable fields add(StringField(updatedAtField, LocalDateTimeFieldConverter.toDoc(updatedAt), Field.Store.YES))
add(StringField(uuidField, UuidFieldConverter.toDoc(note.uuid), Field.Store.YES))
add(StringField(updatedAtField, LocalDateTimeFieldConverter.toDoc(note.updatedAt), Field.Store.YES))
// searchable fields // searchable fields
add(TextField(titleField, note.meta.title, Field.Store.YES)) add(TextField(titleField, title, Field.Store.YES))
add(TextField(tagsField, TagsFieldConverter.toDoc(note.meta.tags), Field.Store.YES)) add(TextField(tagsField, TagsFieldConverter.toDoc(tags), Field.Store.YES))
add(TextField(contentField, note.markdown, Field.Store.YES)) add(TextField(contentField, markdown, Field.Store.YES))
}
} }
internal fun Document.toNoteMeta() = PersistedNoteMetadata( internal fun Document.toNoteMeta() = PersistedNoteMetadata(

View File

@ -16,12 +16,7 @@ import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
internal class NoteSearcherImpl( internal class NoteSearcherImpl(@Named("search-index") basePath: Path) : NoteSearcher {
@Named("search-index")
basePath: Path,
) : NoteSearcher {
constructor() : this(Path.of("/tmp", "lucene"))
private val baseFile = basePath.toFile() private val baseFile = basePath.toFile()

View File

@ -1,5 +1,6 @@
package be.simplenotes.search package be.simplenotes.search
import be.simplenotes.config.DataConfig
import io.micronaut.context.annotation.Factory import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Prototype import io.micronaut.context.annotation.Prototype
import java.nio.file.Path import java.nio.file.Path
@ -10,5 +11,5 @@ class SearchModule {
@Named("search-index") @Named("search-index")
@Prototype @Prototype
internal fun luceneIndex() = Path.of(".lucene") internal fun luceneIndex(dataConfig: DataConfig) = Path.of(dataConfig.dataDir).resolve("index")
} }

View File

@ -1,6 +1,5 @@
package be.simplenotes.search package be.simplenotes.search
import be.simplenotes.types.NoteMetadata
import be.simplenotes.types.PersistedNote import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata import be.simplenotes.types.PersistedNoteMetadata
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -9,6 +8,7 @@ import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.ResourceLock import org.junit.jupiter.api.parallel.ResourceLock
import java.nio.file.Path
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
@ -16,7 +16,7 @@ import java.util.*
internal class NoteSearcherImplTest { internal class NoteSearcherImplTest {
// region setup // region setup
private val searcher = NoteSearcherImpl() private val searcher = NoteSearcherImpl(Path.of("/tmp", "lucene"))
private fun index( private fun index(
title: String, title: String,
@ -25,11 +25,12 @@ internal class NoteSearcherImplTest {
uuid: UUID = UUID.randomUUID(), uuid: UUID = UUID.randomUUID(),
): PersistedNote { ): PersistedNote {
val note = PersistedNote( val note = PersistedNote(
NoteMetadata(title, tags), title = title,
tags = tags,
markdown = content, markdown = content,
html = "", html = "",
LocalDateTime.MIN, updatedAt = LocalDateTime.MIN,
uuid, uuid = uuid,
public = false public = false
) )
searcher.indexNote(1, note) searcher.indexNote(1, note)
@ -150,7 +151,7 @@ internal class NoteSearcherImplTest {
@Test @Test
fun `update index`() { fun `update index`() {
val note = index("first") val note = index("first")
searcher.updateIndex(1, note.copy(meta = note.meta.copy(title = "new"))) searcher.updateIndex(1, note.copy(title = "new"))
assertThat(search("first")).isEmpty() assertThat(search("first")).isEmpty()
assertThat(search("new")).hasSize(1) assertThat(search("new")).hasSize(1)
} }

View File

@ -121,7 +121,7 @@ class NoteView(@Named("styles") styles: String) : View(styles) {
} }
fun renderedNote(loggedInUser: LoggedInUser?, note: PersistedNote, shared: Boolean) = renderPage( fun renderedNote(loggedInUser: LoggedInUser?, note: PersistedNote, shared: Boolean) = renderPage(
note.meta.title, note.title,
loggedInUser = loggedInUser, loggedInUser = loggedInUser,
scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js") scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js")
) { ) {
@ -136,9 +136,9 @@ class NoteView(@Named("styles") styles: String) : View(styles) {
} }
div("flex items-center justify-between mb-4") { div("flex items-center justify-between mb-4") {
h1("text-3xl fond-bold underline") { +note.meta.title } h1("text-3xl fond-bold underline") { +note.title }
span("space-x-2") { span("space-x-2") {
note.meta.tags.forEach { note.tags.forEach {
a(href = "/notes?tag=$it", classes = "tag") { a(href = "/notes?tag=$it", classes = "tag") {
+"#$it" +"#$it"
} }