Update config
This commit is contained in:
parent
204ae7988e
commit
7ad8b7039b
@ -1,6 +1 @@
|
|||||||
# mariadb
|
|
||||||
MYSQL_ROOT_PASSWORD=
|
|
||||||
MYSQL_PASSWORD=
|
|
||||||
# simplenotes
|
|
||||||
DB_PASSWORD=
|
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
|
|||||||
@ -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 [ \
|
||||||
|
|||||||
@ -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() })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
58
config/src/DataConfig.kt
Normal 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")
|
||||||
|
|
||||||
|
}
|
||||||
@ -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:
|
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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()
|
||||||
|
|
||||||
|
|||||||
@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user