Compare commits

...

4 Commits

28 changed files with 211 additions and 224 deletions

View File

@ -1,10 +1,6 @@
## can be generated with `openssl rand -base64 32`
JWT_SECRET=
#
## can be generated with `openssl rand -base64 32`
# mariadb
MYSQL_ROOT_PASSWORD=
#
## can be generated with `openssl rand -base64 32`
MYSQL_PASSWORD=
# password should be the same as mysql_password
PASSWORD=
# simplenotes
DB_PASSWORD=
JWT_SECRET=

View File

@ -15,5 +15,4 @@
## Configuration
The app is configured with environments variables.
If no match is found within the env, a default value is read from a properties file in /app/src/main/resources/application.properties.
Don't use the default values for secrets ! Every value inside *.env.dist* should be changed.
If no match is found within the env, a default value is read from a yaml file in simplenotes-app/src/main/resources/application.yaml.

View File

@ -32,13 +32,13 @@ services:
- .env
environment:
- TZ=Europe/Brussels
- HOST=0.0.0.0
- JDBCURL=jdbc:mariadb://db:3306/simplenotes
- DRIVERCLASSNAME=org.mariadb.jdbc.Driver
- USERNAME=simplenotes
- SERVER_HOST=0.0.0.0
- DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
- DB_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
- DB_USERNAME=simplenotes
# .env:
# - JWT_SECRET
# - PASSWORD
# - DB_PASSWORD
ports:
- 127.0.0.1:8080:8080
healthcheck:

View File

@ -30,9 +30,6 @@ dependencies {
implementation(Libs.micronaut)
kapt(Libs.micronautProcessor)
testImplementation(Libs.micronaut)
kaptTest(Libs.micronautProcessor)
testImplementation(Libs.junit)
testImplementation(Libs.assertJ)
testImplementation(Libs.http4kTestingHamkrest)

View File

@ -1,13 +1,13 @@
package be.simplenotes.app
import io.micronaut.context.annotation.Context
import org.http4k.server.Http4kServer
import org.slf4j.LoggerFactory
import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
import javax.inject.Singleton
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
@Context
@Singleton
class Server(
private val config: SimpleNotesServerConfig,
private val http4kServer: Http4kServer,
@ -17,7 +17,7 @@ class Server(
@PostConstruct
fun start(): Server {
http4kServer.start()
logger.info("Listening on http://${config.host}:${config.port}")
logger.info("Listening on http://${config.host}:${http4kServer.port()}")
return this
}

View File

@ -1,12 +1,10 @@
package be.simplenotes.app
import io.micronaut.context.ApplicationContext
import java.lang.Runtime.getRuntime
fun main() {
val ctx = ApplicationContext.run().start()
Runtime.getRuntime().addShutdownHook(
Thread {
ctx.stop()
}
)
val ctx = ApplicationContext.run()
ctx.createBean(Server::class.java)
getRuntime().addShutdownHook(Thread { ctx.stop() })
}

View File

@ -27,7 +27,7 @@ class ApiNoteController(
fun createNote(request: Request, loggedInUser: LoggedInUser): Response {
val content = noteContentLens(request)
return noteService.create(loggedInUser.userId, content).fold(
return noteService.create(loggedInUser, content).fold(
{ Response(BAD_REQUEST) },
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
)
@ -45,7 +45,7 @@ class ApiNoteController(
fun update(request: Request, loggedInUser: LoggedInUser): Response {
val content = noteContentLens(request)
return noteService.update(loggedInUser.userId, uuidLens(request), content).fold(
return noteService.update(loggedInUser, uuidLens(request), content).fold(
{
Response(BAD_REQUEST)
},

View File

@ -32,7 +32,7 @@ class NoteController(
val markdownForm = request.form("markdown") ?: ""
return noteService.create(loggedInUser.userId, markdownForm).fold(
return noteService.create(loggedInUser, markdownForm).fold(
{
val html = when (it) {
MissingMeta -> view.noteEditor(
@ -112,7 +112,7 @@ class NoteController(
val markdownForm = request.form("markdown") ?: ""
return noteService.update(loggedInUser.userId, note.uuid, markdownForm).fold(
return noteService.update(loggedInUser, note.uuid, markdownForm).fold(
{
val html = when (it) {
MissingMeta -> view.noteEditor(

View File

@ -44,7 +44,7 @@ class ApiRoutes(
"/" bind POST to transaction.then(::createNote),
"/search" bind POST to ::search,
"/{uuid}" bind GET to ::note,
"/{uuid}" bind PUT to transaction.then(::note),
"/{uuid}" bind PUT to transaction.then(::update),
)
).withBasePath("/notes")
}

View File

@ -14,4 +14,5 @@
<logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="org.flywaydb.core" level="INFO"/>
<logger name="io.micronaut" level="INFO"/>
<logger name="io.micronaut.context.lifecycle" level="DEBUG"/>
</configuration>

View File

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

View File

@ -1,47 +0,0 @@
package be.simplenotes.config
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Singleton
class ConfigLoader {
//region Config loading
private val properties: Properties = javaClass
.getResource("/application.properties")
.openStream()
.use {
Properties().apply { load(it) }
}
private val env = System.getenv()
private fun value(key: String): String =
env[key.toUpperCase().replace(".", "_")]
?: properties.getProperty(key)
?: error("Missing config key $key")
//endregion
val jwtConfig
get() = JwtConfig(
secret = value("jwt.secret"),
validity = value("jwt.validity").toLong(),
timeUnit = TimeUnit.HOURS,
)
val dataSourceConfig
get() = DataSourceConfig(
jdbcUrl = value("jdbcUrl"),
driverClassName = value("driverClassName"),
username = value("username"),
password = value("password"),
maximumPoolSize = value("maximumPoolSize").toInt(),
connectionTimeout = value("connectionTimeout").toLong()
)
val serverConfig
get() = ServerConfig(
host = value("host"),
port = value("port").toInt(),
)
}

View File

@ -1,17 +0,0 @@
package be.simplenotes.config
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class ConfigModule {
@Singleton
internal fun dataSourceConfig(configLoader: ConfigLoader) = configLoader.dataSourceConfig
@Singleton
internal fun jwtConfig(configLoader: ConfigLoader) = configLoader.jwtConfig
@Singleton
internal fun serverConfig(configLoader: ConfigLoader) = configLoader.serverConfig
}

View File

@ -1,12 +0,0 @@
host=localhost
port=8080
#
jdbcUrl=jdbc:h2:./notes-db;
driverClassName=org.h2.Driver
username=h2
password=
maximumPoolSize=10
connectionTimeout=3000
#
jwt.secret=PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms=
jwt.validity=24

View File

@ -0,0 +1,16 @@
db:
jdbc-url: jdbc:h2:./notes-db;
driver-class-name: org.h2.Driver
username: h2
password: ''
connection-timeout: 3000
maximum-pool-size: 10
jwt:
secret: 'PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms='
validity: 24
time-unit: hours
server:
host: localhost
port: 8080

View File

@ -1,8 +1,13 @@
package be.simplenotes.domain.security
import be.simplenotes.types.LoggedInUser
import org.owasp.html.HtmlChangeListener
import org.owasp.html.HtmlPolicyBuilder
import org.slf4j.LoggerFactory
import javax.inject.Singleton
internal object HtmlSanitizer {
@Singleton
class HtmlSanitizer {
private val htmlPolicy = HtmlPolicyBuilder()
.allowElements("a")
.allowCommonBlockElements()
@ -16,5 +21,18 @@ internal object HtmlSanitizer {
.requireRelNofollowOnLinks()
.toFactory()!!
fun sanitize(unsafeHtml: String) = htmlPolicy.sanitize(unsafeHtml)!!
private val logger = LoggerFactory.getLogger(javaClass)
private val htmlChangeListener = object : HtmlChangeListener<LoggedInUser> {
override fun discardedTag(context: LoggedInUser?, elementName: String) {
logger.warn("Discarded tag $elementName for user $context")
}
override fun discardedAttributes(context: LoggedInUser?, tagName: String, vararg attributeNames: String) {
logger.warn("Discarded attributes ${attributeNames.contentToString()} on tag $tagName for user $context")
}
}
fun sanitize(userId: LoggedInUser, unsafeHtml: String) =
htmlPolicy.sanitize(unsafeHtml, htmlChangeListener, userId)!!
}

View File

@ -8,10 +8,13 @@ import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.search.NoteSearcher
import be.simplenotes.search.SearchTerms
import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.Note
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import java.util.*
import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
import javax.inject.Singleton
@Singleton
@ -20,25 +23,30 @@ class NoteService(
private val noteRepository: NoteRepository,
private val userRepository: UserRepository,
private val searcher: NoteSearcher,
private val htmlSanitizer: HtmlSanitizer,
) {
fun create(userId: Int, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
fun create(user: LoggedInUser, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
val persistedNote = !markdownConverter.renderDocument(markdownText)
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
.map { noteRepository.create(userId, it) }
.map { noteRepository.create(user.userId, it) }
searcher.indexNote(userId, persistedNote)
searcher.indexNote(user.userId, persistedNote)
persistedNote
}
fun update(userId: Int, uuid: UUID, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote?> {
fun update(
user: LoggedInUser,
uuid: UUID,
markdownText: String,
) = either.eager<MarkdownParsingError, PersistedNote?> {
val persistedNote = !markdownConverter.renderDocument(markdownText)
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
.map { noteRepository.update(userId, uuid, it) }
.map { noteRepository.update(user.userId, uuid, it) }
persistedNote?.let { searcher.updateIndex(userId, it) }
persistedNote?.let { searcher.updateIndex(user.userId, it) }
persistedNote
}
@ -78,7 +86,9 @@ class NoteService(
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
@PostConstruct
fun indexAll() {
dropAllIndexes()
val userIds = userRepository.findAll()
userIds.forEach { id ->
val notes = noteRepository.findAllDetails(id)
@ -88,6 +98,7 @@ class NoteService(
fun search(userId: Int, searchTerms: SearchTerms) = searcher.search(userId, searchTerms)
@PreDestroy
fun dropAllIndexes() = searcher.dropAll()
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)

View File

@ -0,0 +1,34 @@
package be.simplenotes.domain.usecases.markdown
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
import com.vladsch.flexmark.html.HtmlRenderer
import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.data.MutableDataSet
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class FlexmarkFactory {
@Singleton
fun parser(options: MutableDataSet) = Parser.builder(options).build()
@Singleton
fun htmlRenderer(options: MutableDataSet) = HtmlRenderer.builder(options).build()
@Singleton
fun options() = MutableDataSet().apply {
set(Parser.EXTENSIONS, listOf(TaskListExtension.create()))
set(TaskListExtension.TIGHT_ITEM_CLASS, "")
set(TaskListExtension.LOOSE_ITEM_CLASS, "")
set(
TaskListExtension.ITEM_DONE_MARKER,
"""<input type="checkbox" checked="checked" disabled="disabled" readonly="readonly" />&nbsp;"""
)
set(
TaskListExtension.ITEM_NOT_DONE_MARKER,
"""<input type="checkbox" disabled="disabled" readonly="readonly" />&nbsp;"""
)
set(HtmlRenderer.SOFT_BREAK, "<br>")
}
}

View File

@ -1,20 +1,8 @@
package be.simplenotes.domain.usecases.markdown
import arrow.core.Either
import arrow.core.computations.either
import arrow.core.left
import arrow.core.right
import be.simplenotes.domain.validation.NoteValidations
import be.simplenotes.types.NoteMetadata
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
import com.vladsch.flexmark.html.HtmlRenderer
import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.data.MutableDataSet
import io.konform.validation.ValidationErrors
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.parser.ParserException
import org.yaml.snakeyaml.scanner.ScannerException
import javax.inject.Singleton
sealed class MarkdownParsingError
object MissingMeta : MarkdownParsingError()
@ -23,63 +11,6 @@ class ValidationError(val validationErrors: ValidationErrors) : MarkdownParsingE
data class Document(val metadata: NoteMetadata, val html: String)
typealias MetaMdPair = Pair<String, String>
interface MarkdownConverter {
fun renderDocument(input: String): Either<MarkdownParsingError, Document>
}
@Singleton
internal class MarkdownConverterImpl : MarkdownConverter {
private val yamlBoundPattern = "-{3}".toRegex()
private fun splitMetaFromDocument(input: String): Either<MissingMeta, MetaMdPair> {
val split = input.split(yamlBoundPattern, 3)
if (split.size < 3) return MissingMeta.left()
return (split[1].trim() to split[2].trim()).right()
}
private val yaml = Yaml()
private fun parseMeta(input: String): Either<InvalidMeta, NoteMetadata> {
val load: Map<String, Any> = try {
yaml.load(input)
} catch (e: ParserException) {
return InvalidMeta.left()
} catch (e: ScannerException) {
return InvalidMeta.left()
}
val title = when (val titleNode = load["title"]) {
is String, is Number -> titleNode.toString()
else -> return InvalidMeta.left()
}
val tagsNode = load["tags"]
val tags = if (tagsNode !is List<*>)
emptyList()
else
tagsNode.map { it.toString() }
return NoteMetadata(title, tags).right()
}
private val parser: Parser
private val renderer: HtmlRenderer
init {
val options = MutableDataSet()
options.set(Parser.EXTENSIONS, listOf(TaskListExtension.create()))
options.set(HtmlRenderer.SOFT_BREAK, "<br>")
parser = Parser.builder(options).build()
renderer = HtmlRenderer.builder(options).build()
}
private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render)
override fun renderDocument(input: String) = either.eager<MarkdownParsingError, Document> {
val (meta, md) = !splitMetaFromDocument(input)
val parsedMeta = !parseMeta(meta)
!Either.fromNullable(NoteValidations.validateMetadata(parsedMeta)).swap()
val html = renderMarkdown(md)
Document(parsedMeta, html)
}
}

View File

@ -0,0 +1,63 @@
package be.simplenotes.domain.usecases.markdown
import arrow.core.Either
import arrow.core.computations.either
import arrow.core.left
import arrow.core.right
import be.simplenotes.domain.validation.NoteValidations
import be.simplenotes.types.NoteMetadata
import com.vladsch.flexmark.html.HtmlRenderer
import com.vladsch.flexmark.parser.Parser
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.parser.ParserException
import org.yaml.snakeyaml.scanner.ScannerException
import javax.inject.Singleton
private typealias MetaMdPair = Pair<String, String>
@Singleton
internal class MarkdownConverterImpl(
private val parser: Parser,
private val renderer: HtmlRenderer,
) : MarkdownConverter {
private val yamlBoundPattern = "-{3}".toRegex()
private fun splitMetaFromDocument(input: String): Either<MissingMeta, MetaMdPair> {
val split = input.split(yamlBoundPattern, 3)
if (split.size < 3) return MissingMeta.left()
return (split[1].trim() to split[2].trim()).right()
}
private val yaml = Yaml()
private fun parseMeta(input: String): Either<InvalidMeta, NoteMetadata> {
val load: Map<String, Any> = try {
yaml.load(input)
} catch (e: ParserException) {
return InvalidMeta.left()
} catch (e: ScannerException) {
return InvalidMeta.left()
}
val title = when (val titleNode = load["title"]) {
is String, is Number -> titleNode.toString()
else -> return InvalidMeta.left()
}
val tagsNode = load["tags"]
val tags = if (tagsNode !is List<*>)
emptyList()
else
tagsNode.map { it.toString() }
return NoteMetadata(title, tags).right()
}
private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render)
override fun renderDocument(input: String) = either.eager<MarkdownParsingError, Document> {
val (meta, md) = !splitMetaFromDocument(input)
val parsedMeta = !parseMeta(meta)
!Either.fromNullable(NoteValidations.validateMetadata(parsedMeta)).swap()
val html = renderMarkdown(md)
Document(parsedMeta, html)
}
}

View File

@ -16,16 +16,14 @@ dependencies {
implementation(Libs.hikariCP)
implementation(Libs.ktormCore)
implementation(Libs.ktormMysql)
implementation(Libs.logbackClassic)
implementation(Libs.mapstruct)
compileOnly(Libs.mapstruct)
kapt(Libs.mapstructProcessor)
implementation(Libs.micronaut)
kapt(Libs.micronautProcessor)
testImplementation(Libs.micronaut)
kaptTest(Libs.micronautProcessor)
testImplementation(Libs.junit)
testImplementation(Libs.assertJ)
testImplementation(Libs.logbackClassic)

View File

@ -6,6 +6,7 @@ import be.simplenotes.persistance.utils.type
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.database.asIterable
import me.liuwj.ktorm.database.use
import java.io.EOFException
import java.sql.SQLTransientException
import javax.inject.Singleton
@ -27,5 +28,7 @@ internal class DbHealthCheckImpl(
}.any { it in dataSourceConfig.jdbcUrl }
} catch (e: SQLTransientException) {
false
} catch (e: EOFException) {
false
}
}

View File

@ -1,26 +1,17 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.converters.UserConverter
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import me.liuwj.ktorm.database.Database
import org.mapstruct.factory.Mappers
import javax.inject.Singleton
import javax.sql.DataSource
@Factory
class PersistanceModule {
@Singleton
internal fun noteConverter() = Mappers.getMapper(NoteConverter::class.java)
@Singleton
internal fun userConverter() = Mappers.getMapper(UserConverter::class.java)
@Singleton
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
migrations.migrate()

View File

@ -9,12 +9,13 @@ import org.mapstruct.Mappings
import org.mapstruct.ReportingPolicy
import java.time.LocalDateTime
import java.util.*
import javax.inject.Singleton
/**
* This is an abstract class because kotlin default methods in interface are not seen as default in kapt
* @see [KT-25960](https://youtrack.jetbrains.com/issue/KT-25960)
*/
@Mapper(uses = [NoteEntityFactory::class, UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Mapper(
uses = [NoteEntityFactory::class, UserEntityFactory::class],
unmappedTargetPolicy = ReportingPolicy.IGNORE,
componentModel = "jsr330"
)
internal abstract class NoteConverter {
fun toNote(entity: NoteEntity, tags: Tags) =
@ -80,4 +81,5 @@ internal abstract class NoteConverter {
typealias Tags = List<String>
@Singleton
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()

View File

@ -6,8 +6,13 @@ import be.simplenotes.types.User
import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper
import org.mapstruct.ReportingPolicy
import javax.inject.Singleton
@Mapper(uses = [UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Mapper(
uses = [UserEntityFactory::class],
unmappedTargetPolicy = ReportingPolicy.IGNORE,
componentModel = "jsr330"
)
internal interface UserConverter {
fun toUser(userEntity: UserEntity): User
fun toPersistedUser(userEntity: UserEntity): PersistedUser
@ -15,4 +20,5 @@ internal interface UserConverter {
fun toEntity(user: PersistedUser): UserEntity
}
@Singleton
internal class UserEntityFactory : Entity.Factory<UserEntity>()

View File

@ -2,23 +2,25 @@ package be.simplenotes.persistance.converters
import be.simplenotes.persistance.notes.NoteEntity
import be.simplenotes.types.*
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.mapstruct.factory.Mappers
import java.time.LocalDateTime
import java.util.*
internal class NoteConverterTest {
private val ctx = BeanContext.run()
val converter = ctx.getBean(NoteConverter::class.java)
@Nested
@DisplayName("Entity -> Models")
inner class EntityToModels {
@Test
fun `convert NoteEntity to Note`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity {
title = "title"
markdown = "md"
@ -39,7 +41,6 @@ internal class NoteConverterTest {
@Test
fun `convert NoteEntity to ExportedNote`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity {
title = "title"
markdown = "md"
@ -62,7 +63,6 @@ internal class NoteConverterTest {
@Test
fun `convert NoteEntity to PersistedNoteMetadata`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity {
uuid = UUID.randomUUID()
title = "title"
@ -89,7 +89,6 @@ internal class NoteConverterTest {
@Test
fun `convert Note to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
@ -103,7 +102,6 @@ internal class NoteConverterTest {
@Test
fun `convert PersistedNoteMetadata to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val persistedNoteMetadata =
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
val entity = converter.toEntity(persistedNoteMetadata)
@ -116,7 +114,6 @@ internal class NoteConverterTest {
@Test
fun `convert NoteMetadata to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val noteMetadata = NoteMetadata("title", emptyList())
val entity = converter.toEntity(noteMetadata)
@ -126,7 +123,6 @@ internal class NoteConverterTest {
@Test
fun `convert PersistedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val persistedNote = PersistedNote(
NoteMetadata("title", emptyList()),
markdown = "md",
@ -148,7 +144,6 @@ internal class NoteConverterTest {
@Test
fun `convert ExportedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val exportedNote = ExportedNote(
"title",
emptyList(),

View File

@ -3,15 +3,17 @@ package be.simplenotes.persistance.converters
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mapstruct.factory.Mappers
internal class UserConverterTest {
private val ctx = BeanContext.run()
private val converter = ctx.getBean(UserConverter::class.java)
@Test
fun `convert UserEntity to User`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity {
username = "test"
password = "test2"
@ -24,7 +26,6 @@ internal class UserConverterTest {
@Test
fun `convert UserEntity to PersistedUser`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity {
username = "test"
password = "test2"
@ -37,7 +38,6 @@ internal class UserConverterTest {
@Test
fun `convert User to UserEntity`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val user = User("test", "test2")
val entity = converter.toEntity(user)

View File

@ -16,7 +16,6 @@ import me.liuwj.ktorm.entity.toList
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.*
import org.mapstruct.factory.Mappers
import java.sql.SQLIntegrityConstraintViolationException
internal abstract class BaseNoteRepositoryImplTest : DbTest() {
@ -142,7 +141,7 @@ internal abstract class BaseNoteRepositoryImplTest : DbTest() {
fun `find an existing note`() {
val fakeNote = noteRepo.insertFakeNote(user1)
val converter = Mappers.getMapper(NoteConverter::class.java)
val converter = beanContext.getBean(NoteConverter::class.java)
val note = db.notes.find { it.title eq fakeNote.meta.title }!!
.let { entity ->