Compare commits
No commits in common. "11caff1634d0291370ba11d68db5eaa07e53f4a7" and "4f395d254da25c2157966d5744d062811bfb0eb3" have entirely different histories.
11caff1634
...
4f395d254d
14
.env.dist
14
.env.dist
@ -1,6 +1,10 @@
|
|||||||
# mariadb
|
## can be generated with `openssl rand -base64 32`
|
||||||
MYSQL_ROOT_PASSWORD=
|
|
||||||
MYSQL_PASSWORD=
|
|
||||||
# simplenotes
|
|
||||||
DB_PASSWORD=
|
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
|
#
|
||||||
|
## can be generated with `openssl rand -base64 32`
|
||||||
|
MYSQL_ROOT_PASSWORD=
|
||||||
|
#
|
||||||
|
## can be generated with `openssl rand -base64 32`
|
||||||
|
MYSQL_PASSWORD=
|
||||||
|
# password should be the same as mysql_password
|
||||||
|
PASSWORD=
|
||||||
|
|||||||
@ -15,4 +15,5 @@
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The app is configured with environments variables.
|
The app is configured with environments variables.
|
||||||
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.
|
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.
|
||||||
|
|||||||
@ -32,13 +32,13 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Brussels
|
- TZ=Europe/Brussels
|
||||||
- SERVER_HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
|
- JDBCURL=jdbc:mariadb://db:3306/simplenotes
|
||||||
- DB_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
|
- DRIVERCLASSNAME=org.mariadb.jdbc.Driver
|
||||||
- DB_USERNAME=simplenotes
|
- USERNAME=simplenotes
|
||||||
# .env:
|
# .env:
|
||||||
# - JWT_SECRET
|
# - JWT_SECRET
|
||||||
# - DB_PASSWORD
|
# - PASSWORD
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:8080:8080
|
- 127.0.0.1:8080:8080
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@ -30,6 +30,9 @@ dependencies {
|
|||||||
implementation(Libs.micronaut)
|
implementation(Libs.micronaut)
|
||||||
kapt(Libs.micronautProcessor)
|
kapt(Libs.micronautProcessor)
|
||||||
|
|
||||||
|
testImplementation(Libs.micronaut)
|
||||||
|
kaptTest(Libs.micronautProcessor)
|
||||||
|
|
||||||
testImplementation(Libs.junit)
|
testImplementation(Libs.junit)
|
||||||
testImplementation(Libs.assertJ)
|
testImplementation(Libs.assertJ)
|
||||||
testImplementation(Libs.http4kTestingHamkrest)
|
testImplementation(Libs.http4kTestingHamkrest)
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
package be.simplenotes.app
|
package be.simplenotes.app
|
||||||
|
|
||||||
|
import io.micronaut.context.annotation.Context
|
||||||
import org.http4k.server.Http4kServer
|
import org.http4k.server.Http4kServer
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import javax.annotation.PostConstruct
|
import javax.annotation.PostConstruct
|
||||||
import javax.annotation.PreDestroy
|
import javax.annotation.PreDestroy
|
||||||
import javax.inject.Singleton
|
|
||||||
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
||||||
|
|
||||||
@Singleton
|
@Context
|
||||||
class Server(
|
class Server(
|
||||||
private val config: SimpleNotesServerConfig,
|
private val config: SimpleNotesServerConfig,
|
||||||
private val http4kServer: Http4kServer,
|
private val http4kServer: Http4kServer,
|
||||||
@ -17,7 +17,7 @@ class Server(
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
fun start(): Server {
|
fun start(): Server {
|
||||||
http4kServer.start()
|
http4kServer.start()
|
||||||
logger.info("Listening on http://${config.host}:${http4kServer.port()}")
|
logger.info("Listening on http://${config.host}:${config.port}")
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
package be.simplenotes.app
|
package be.simplenotes.app
|
||||||
|
|
||||||
import io.micronaut.context.ApplicationContext
|
import io.micronaut.context.ApplicationContext
|
||||||
import java.lang.Runtime.getRuntime
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val ctx = ApplicationContext.run()
|
val ctx = ApplicationContext.run().start()
|
||||||
ctx.createBean(Server::class.java)
|
Runtime.getRuntime().addShutdownHook(
|
||||||
getRuntime().addShutdownHook(Thread { ctx.stop() })
|
Thread {
|
||||||
|
ctx.stop()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class ApiNoteController(
|
|||||||
|
|
||||||
fun createNote(request: Request, loggedInUser: LoggedInUser): Response {
|
fun createNote(request: Request, loggedInUser: LoggedInUser): Response {
|
||||||
val content = noteContentLens(request)
|
val content = noteContentLens(request)
|
||||||
return noteService.create(loggedInUser, content).fold(
|
return noteService.create(loggedInUser.userId, content).fold(
|
||||||
{ Response(BAD_REQUEST) },
|
{ Response(BAD_REQUEST) },
|
||||||
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
|
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
|
||||||
)
|
)
|
||||||
@ -45,7 +45,7 @@ class ApiNoteController(
|
|||||||
|
|
||||||
fun update(request: Request, loggedInUser: LoggedInUser): Response {
|
fun update(request: Request, loggedInUser: LoggedInUser): Response {
|
||||||
val content = noteContentLens(request)
|
val content = noteContentLens(request)
|
||||||
return noteService.update(loggedInUser, uuidLens(request), content).fold(
|
return noteService.update(loggedInUser.userId, uuidLens(request), content).fold(
|
||||||
{
|
{
|
||||||
Response(BAD_REQUEST)
|
Response(BAD_REQUEST)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class NoteController(
|
|||||||
|
|
||||||
val markdownForm = request.form("markdown") ?: ""
|
val markdownForm = request.form("markdown") ?: ""
|
||||||
|
|
||||||
return noteService.create(loggedInUser, markdownForm).fold(
|
return noteService.create(loggedInUser.userId, markdownForm).fold(
|
||||||
{
|
{
|
||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
MissingMeta -> view.noteEditor(
|
MissingMeta -> view.noteEditor(
|
||||||
@ -112,7 +112,7 @@ class NoteController(
|
|||||||
|
|
||||||
val markdownForm = request.form("markdown") ?: ""
|
val markdownForm = request.form("markdown") ?: ""
|
||||||
|
|
||||||
return noteService.update(loggedInUser, note.uuid, markdownForm).fold(
|
return noteService.update(loggedInUser.userId, note.uuid, markdownForm).fold(
|
||||||
{
|
{
|
||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
MissingMeta -> view.noteEditor(
|
MissingMeta -> view.noteEditor(
|
||||||
|
|||||||
@ -44,7 +44,7 @@ class ApiRoutes(
|
|||||||
"/" bind POST to transaction.then(::createNote),
|
"/" bind POST to transaction.then(::createNote),
|
||||||
"/search" bind POST to ::search,
|
"/search" bind POST to ::search,
|
||||||
"/{uuid}" bind GET to ::note,
|
"/{uuid}" bind GET to ::note,
|
||||||
"/{uuid}" bind PUT to transaction.then(::update),
|
"/{uuid}" bind PUT to transaction.then(::note),
|
||||||
)
|
)
|
||||||
).withBasePath("/notes")
|
).withBasePath("/notes")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,5 +14,4 @@
|
|||||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||||
<logger name="org.flywaydb.core" level="INFO"/>
|
<logger name="org.flywaydb.core" level="INFO"/>
|
||||||
<logger name="io.micronaut" level="INFO"/>
|
<logger name="io.micronaut" level="INFO"/>
|
||||||
<logger name="io.micronaut.context.lifecycle" level="DEBUG"/>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
package be.simplenotes.config
|
package be.simplenotes.config
|
||||||
|
|
||||||
import io.micronaut.context.annotation.ConfigurationInject
|
|
||||||
import io.micronaut.context.annotation.ConfigurationProperties
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@ConfigurationProperties("db")
|
data class DataSourceConfig(
|
||||||
data class DataSourceConfig @ConfigurationInject constructor(
|
|
||||||
val jdbcUrl: String,
|
val jdbcUrl: String,
|
||||||
val driverClassName: String,
|
val driverClassName: String,
|
||||||
val username: String,
|
val username: String,
|
||||||
@ -17,8 +14,7 @@ data class DataSourceConfig @ConfigurationInject constructor(
|
|||||||
"username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)"
|
"username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurationProperties("jwt")
|
data class JwtConfig(
|
||||||
data class JwtConfig @ConfigurationInject constructor(
|
|
||||||
val secret: String,
|
val secret: String,
|
||||||
val validity: Long,
|
val validity: Long,
|
||||||
val timeUnit: TimeUnit,
|
val timeUnit: TimeUnit,
|
||||||
@ -26,8 +22,7 @@ data class JwtConfig @ConfigurationInject constructor(
|
|||||||
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
|
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurationProperties("server")
|
data class ServerConfig(
|
||||||
data class ServerConfig @ConfigurationInject constructor(
|
|
||||||
val host: String,
|
val host: String,
|
||||||
val port: Int,
|
val port: Int,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
12
simplenotes-config/src/main/resources/application.properties
Normal file
12
simplenotes-config/src/main/resources/application.properties
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,13 +1,8 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import org.owasp.html.HtmlChangeListener
|
|
||||||
import org.owasp.html.HtmlPolicyBuilder
|
import org.owasp.html.HtmlPolicyBuilder
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
internal object HtmlSanitizer {
|
||||||
class HtmlSanitizer {
|
|
||||||
private val htmlPolicy = HtmlPolicyBuilder()
|
private val htmlPolicy = HtmlPolicyBuilder()
|
||||||
.allowElements("a")
|
.allowElements("a")
|
||||||
.allowCommonBlockElements()
|
.allowCommonBlockElements()
|
||||||
@ -21,18 +16,5 @@ class HtmlSanitizer {
|
|||||||
.requireRelNofollowOnLinks()
|
.requireRelNofollowOnLinks()
|
||||||
.toFactory()!!
|
.toFactory()!!
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
fun sanitize(unsafeHtml: String) = htmlPolicy.sanitize(unsafeHtml)!!
|
||||||
|
|
||||||
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)!!
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,13 +8,10 @@ import be.simplenotes.persistance.repositories.NoteRepository
|
|||||||
import be.simplenotes.persistance.repositories.UserRepository
|
import be.simplenotes.persistance.repositories.UserRepository
|
||||||
import be.simplenotes.search.NoteSearcher
|
import be.simplenotes.search.NoteSearcher
|
||||||
import be.simplenotes.search.SearchTerms
|
import be.simplenotes.search.SearchTerms
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import be.simplenotes.types.Note
|
import be.simplenotes.types.Note
|
||||||
import be.simplenotes.types.PersistedNote
|
import be.simplenotes.types.PersistedNote
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.annotation.PostConstruct
|
|
||||||
import javax.annotation.PreDestroy
|
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -23,30 +20,25 @@ 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,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(user: LoggedInUser, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
|
fun create(userId: Int, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
|
||||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
|
||||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||||
.map { noteRepository.create(user.userId, it) }
|
.map { noteRepository.create(userId, it) }
|
||||||
|
|
||||||
searcher.indexNote(user.userId, persistedNote)
|
searcher.indexNote(userId, persistedNote)
|
||||||
persistedNote
|
persistedNote
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(
|
fun update(userId: Int, uuid: UUID, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote?> {
|
||||||
user: LoggedInUser,
|
|
||||||
uuid: UUID,
|
|
||||||
markdownText: String,
|
|
||||||
) = either.eager<MarkdownParsingError, PersistedNote?> {
|
|
||||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
|
||||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||||
.map { noteRepository.update(user.userId, uuid, it) }
|
.map { noteRepository.update(userId, uuid, it) }
|
||||||
|
|
||||||
persistedNote?.let { searcher.updateIndex(user.userId, it) }
|
persistedNote?.let { searcher.updateIndex(userId, it) }
|
||||||
persistedNote
|
persistedNote
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,9 +78,7 @@ class NoteService(
|
|||||||
|
|
||||||
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
|
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
fun indexAll() {
|
fun indexAll() {
|
||||||
dropAllIndexes()
|
|
||||||
val userIds = userRepository.findAll()
|
val userIds = userRepository.findAll()
|
||||||
userIds.forEach { id ->
|
userIds.forEach { id ->
|
||||||
val notes = noteRepository.findAllDetails(id)
|
val notes = noteRepository.findAllDetails(id)
|
||||||
@ -98,7 +88,6 @@ class NoteService(
|
|||||||
|
|
||||||
fun search(userId: Int, searchTerms: SearchTerms) = searcher.search(userId, searchTerms)
|
fun search(userId: Int, searchTerms: SearchTerms) = searcher.search(userId, searchTerms)
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
fun dropAllIndexes() = searcher.dropAll()
|
fun dropAllIndexes() = searcher.dropAll()
|
||||||
|
|
||||||
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)
|
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
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" /> """
|
|
||||||
)
|
|
||||||
set(
|
|
||||||
TaskListExtension.ITEM_NOT_DONE_MARKER,
|
|
||||||
"""<input type="checkbox" disabled="disabled" readonly="readonly" /> """
|
|
||||||
)
|
|
||||||
set(HtmlRenderer.SOFT_BREAK, "<br>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,20 @@
|
|||||||
package be.simplenotes.domain.usecases.markdown
|
package be.simplenotes.domain.usecases.markdown
|
||||||
|
|
||||||
import arrow.core.Either
|
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 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 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
|
sealed class MarkdownParsingError
|
||||||
object MissingMeta : MarkdownParsingError()
|
object MissingMeta : MarkdownParsingError()
|
||||||
@ -11,6 +23,63 @@ class ValidationError(val validationErrors: ValidationErrors) : MarkdownParsingE
|
|||||||
|
|
||||||
data class Document(val metadata: NoteMetadata, val html: String)
|
data class Document(val metadata: NoteMetadata, val html: String)
|
||||||
|
|
||||||
|
typealias MetaMdPair = Pair<String, String>
|
||||||
|
|
||||||
interface MarkdownConverter {
|
interface MarkdownConverter {
|
||||||
fun renderDocument(input: String): Either<MarkdownParsingError, Document>
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,14 +16,16 @@ dependencies {
|
|||||||
implementation(Libs.hikariCP)
|
implementation(Libs.hikariCP)
|
||||||
implementation(Libs.ktormCore)
|
implementation(Libs.ktormCore)
|
||||||
implementation(Libs.ktormMysql)
|
implementation(Libs.ktormMysql)
|
||||||
implementation(Libs.logbackClassic)
|
|
||||||
|
|
||||||
compileOnly(Libs.mapstruct)
|
implementation(Libs.mapstruct)
|
||||||
kapt(Libs.mapstructProcessor)
|
kapt(Libs.mapstructProcessor)
|
||||||
|
|
||||||
implementation(Libs.micronaut)
|
implementation(Libs.micronaut)
|
||||||
kapt(Libs.micronautProcessor)
|
kapt(Libs.micronautProcessor)
|
||||||
|
|
||||||
|
testImplementation(Libs.micronaut)
|
||||||
|
kaptTest(Libs.micronautProcessor)
|
||||||
|
|
||||||
testImplementation(Libs.junit)
|
testImplementation(Libs.junit)
|
||||||
testImplementation(Libs.assertJ)
|
testImplementation(Libs.assertJ)
|
||||||
testImplementation(Libs.logbackClassic)
|
testImplementation(Libs.logbackClassic)
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import be.simplenotes.persistance.utils.type
|
|||||||
import me.liuwj.ktorm.database.Database
|
import me.liuwj.ktorm.database.Database
|
||||||
import me.liuwj.ktorm.database.asIterable
|
import me.liuwj.ktorm.database.asIterable
|
||||||
import me.liuwj.ktorm.database.use
|
import me.liuwj.ktorm.database.use
|
||||||
import java.io.EOFException
|
|
||||||
import java.sql.SQLTransientException
|
import java.sql.SQLTransientException
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -28,7 +27,5 @@ internal class DbHealthCheckImpl(
|
|||||||
}.any { it in dataSourceConfig.jdbcUrl }
|
}.any { it in dataSourceConfig.jdbcUrl }
|
||||||
} catch (e: SQLTransientException) {
|
} catch (e: SQLTransientException) {
|
||||||
false
|
false
|
||||||
} catch (e: EOFException) {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,26 @@
|
|||||||
package be.simplenotes.persistance
|
package be.simplenotes.persistance
|
||||||
|
|
||||||
import be.simplenotes.config.DataSourceConfig
|
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.HikariConfig
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import io.micronaut.context.annotation.Bean
|
import io.micronaut.context.annotation.Bean
|
||||||
import io.micronaut.context.annotation.Factory
|
import io.micronaut.context.annotation.Factory
|
||||||
import me.liuwj.ktorm.database.Database
|
import me.liuwj.ktorm.database.Database
|
||||||
|
import org.mapstruct.factory.Mappers
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import javax.sql.DataSource
|
import javax.sql.DataSource
|
||||||
|
|
||||||
@Factory
|
@Factory
|
||||||
class PersistanceModule {
|
class PersistanceModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
internal fun noteConverter() = Mappers.getMapper(NoteConverter::class.java)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
internal fun userConverter() = Mappers.getMapper(UserConverter::class.java)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
|
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
|
||||||
migrations.migrate()
|
migrations.migrate()
|
||||||
|
|||||||
@ -9,13 +9,12 @@ import org.mapstruct.Mappings
|
|||||||
import org.mapstruct.ReportingPolicy
|
import org.mapstruct.ReportingPolicy
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Mapper(
|
/**
|
||||||
uses = [NoteEntityFactory::class, UserEntityFactory::class],
|
* This is an abstract class because kotlin default methods in interface are not seen as default in kapt
|
||||||
unmappedTargetPolicy = ReportingPolicy.IGNORE,
|
* @see [KT-25960](https://youtrack.jetbrains.com/issue/KT-25960)
|
||||||
componentModel = "jsr330"
|
*/
|
||||||
)
|
@Mapper(uses = [NoteEntityFactory::class, UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
internal abstract class NoteConverter {
|
internal abstract class NoteConverter {
|
||||||
|
|
||||||
fun toNote(entity: NoteEntity, tags: Tags) =
|
fun toNote(entity: NoteEntity, tags: Tags) =
|
||||||
@ -81,5 +80,4 @@ internal abstract class NoteConverter {
|
|||||||
|
|
||||||
typealias Tags = List<String>
|
typealias Tags = List<String>
|
||||||
|
|
||||||
@Singleton
|
|
||||||
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
|
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
|
||||||
|
|||||||
@ -6,13 +6,8 @@ import be.simplenotes.types.User
|
|||||||
import me.liuwj.ktorm.entity.Entity
|
import me.liuwj.ktorm.entity.Entity
|
||||||
import org.mapstruct.Mapper
|
import org.mapstruct.Mapper
|
||||||
import org.mapstruct.ReportingPolicy
|
import org.mapstruct.ReportingPolicy
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Mapper(
|
@Mapper(uses = [UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
uses = [UserEntityFactory::class],
|
|
||||||
unmappedTargetPolicy = ReportingPolicy.IGNORE,
|
|
||||||
componentModel = "jsr330"
|
|
||||||
)
|
|
||||||
internal interface UserConverter {
|
internal interface UserConverter {
|
||||||
fun toUser(userEntity: UserEntity): User
|
fun toUser(userEntity: UserEntity): User
|
||||||
fun toPersistedUser(userEntity: UserEntity): PersistedUser
|
fun toPersistedUser(userEntity: UserEntity): PersistedUser
|
||||||
@ -20,5 +15,4 @@ internal interface UserConverter {
|
|||||||
fun toEntity(user: PersistedUser): UserEntity
|
fun toEntity(user: PersistedUser): UserEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
|
||||||
internal class UserEntityFactory : Entity.Factory<UserEntity>()
|
internal class UserEntityFactory : Entity.Factory<UserEntity>()
|
||||||
|
|||||||
@ -2,25 +2,23 @@ package be.simplenotes.persistance.converters
|
|||||||
|
|
||||||
import be.simplenotes.persistance.notes.NoteEntity
|
import be.simplenotes.persistance.notes.NoteEntity
|
||||||
import be.simplenotes.types.*
|
import be.simplenotes.types.*
|
||||||
import io.micronaut.context.BeanContext
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mapstruct.factory.Mappers
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal class NoteConverterTest {
|
internal class NoteConverterTest {
|
||||||
|
|
||||||
private val ctx = BeanContext.run()
|
|
||||||
val converter = ctx.getBean(NoteConverter::class.java)
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@DisplayName("Entity -> Models")
|
@DisplayName("Entity -> Models")
|
||||||
inner class EntityToModels {
|
inner class EntityToModels {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert NoteEntity to Note`() {
|
fun `convert NoteEntity to Note`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val entity = NoteEntity {
|
val entity = NoteEntity {
|
||||||
title = "title"
|
title = "title"
|
||||||
markdown = "md"
|
markdown = "md"
|
||||||
@ -41,6 +39,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert NoteEntity to ExportedNote`() {
|
fun `convert NoteEntity to ExportedNote`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val entity = NoteEntity {
|
val entity = NoteEntity {
|
||||||
title = "title"
|
title = "title"
|
||||||
markdown = "md"
|
markdown = "md"
|
||||||
@ -63,6 +62,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert NoteEntity to PersistedNoteMetadata`() {
|
fun `convert NoteEntity to PersistedNoteMetadata`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val entity = NoteEntity {
|
val entity = NoteEntity {
|
||||||
uuid = UUID.randomUUID()
|
uuid = UUID.randomUUID()
|
||||||
title = "title"
|
title = "title"
|
||||||
@ -89,6 +89,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert Note to NoteEntity`() {
|
fun `convert Note to NoteEntity`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
||||||
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
|
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert PersistedNoteMetadata to NoteEntity`() {
|
fun `convert PersistedNoteMetadata to NoteEntity`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val persistedNoteMetadata =
|
val persistedNoteMetadata =
|
||||||
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
|
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
|
||||||
val entity = converter.toEntity(persistedNoteMetadata)
|
val entity = converter.toEntity(persistedNoteMetadata)
|
||||||
@ -114,6 +116,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert NoteMetadata to NoteEntity`() {
|
fun `convert NoteMetadata to NoteEntity`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val noteMetadata = NoteMetadata("title", emptyList())
|
val noteMetadata = NoteMetadata("title", emptyList())
|
||||||
val entity = converter.toEntity(noteMetadata)
|
val entity = converter.toEntity(noteMetadata)
|
||||||
|
|
||||||
@ -123,6 +126,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert PersistedNote to NoteEntity`() {
|
fun `convert PersistedNote to NoteEntity`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val persistedNote = PersistedNote(
|
val persistedNote = PersistedNote(
|
||||||
NoteMetadata("title", emptyList()),
|
NoteMetadata("title", emptyList()),
|
||||||
markdown = "md",
|
markdown = "md",
|
||||||
@ -144,6 +148,7 @@ internal class NoteConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert ExportedNote to NoteEntity`() {
|
fun `convert ExportedNote to NoteEntity`() {
|
||||||
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
val exportedNote = ExportedNote(
|
val exportedNote = ExportedNote(
|
||||||
"title",
|
"title",
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
|||||||
@ -3,17 +3,15 @@ package be.simplenotes.persistance.converters
|
|||||||
import be.simplenotes.persistance.users.UserEntity
|
import be.simplenotes.persistance.users.UserEntity
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.types.PersistedUser
|
||||||
import be.simplenotes.types.User
|
import be.simplenotes.types.User
|
||||||
import io.micronaut.context.BeanContext
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mapstruct.factory.Mappers
|
||||||
|
|
||||||
internal class UserConverterTest {
|
internal class UserConverterTest {
|
||||||
|
|
||||||
private val ctx = BeanContext.run()
|
|
||||||
private val converter = ctx.getBean(UserConverter::class.java)
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert UserEntity to User`() {
|
fun `convert UserEntity to User`() {
|
||||||
|
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||||
val entity = UserEntity {
|
val entity = UserEntity {
|
||||||
username = "test"
|
username = "test"
|
||||||
password = "test2"
|
password = "test2"
|
||||||
@ -26,6 +24,7 @@ internal class UserConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert UserEntity to PersistedUser`() {
|
fun `convert UserEntity to PersistedUser`() {
|
||||||
|
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||||
val entity = UserEntity {
|
val entity = UserEntity {
|
||||||
username = "test"
|
username = "test"
|
||||||
password = "test2"
|
password = "test2"
|
||||||
@ -38,6 +37,7 @@ internal class UserConverterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `convert User to UserEntity`() {
|
fun `convert User to UserEntity`() {
|
||||||
|
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||||
val user = User("test", "test2")
|
val user = User("test", "test2")
|
||||||
val entity = converter.toEntity(user)
|
val entity = converter.toEntity(user)
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import me.liuwj.ktorm.entity.toList
|
|||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.jupiter.api.*
|
import org.junit.jupiter.api.*
|
||||||
|
import org.mapstruct.factory.Mappers
|
||||||
import java.sql.SQLIntegrityConstraintViolationException
|
import java.sql.SQLIntegrityConstraintViolationException
|
||||||
|
|
||||||
internal abstract class BaseNoteRepositoryImplTest : DbTest() {
|
internal abstract class BaseNoteRepositoryImplTest : DbTest() {
|
||||||
@ -141,7 +142,7 @@ internal abstract class BaseNoteRepositoryImplTest : DbTest() {
|
|||||||
fun `find an existing note`() {
|
fun `find an existing note`() {
|
||||||
val fakeNote = noteRepo.insertFakeNote(user1)
|
val fakeNote = noteRepo.insertFakeNote(user1)
|
||||||
|
|
||||||
val converter = beanContext.getBean(NoteConverter::class.java)
|
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||||
|
|
||||||
val note = db.notes.find { it.title eq fakeNote.meta.title }!!
|
val note = db.notes.find { it.title eq fakeNote.meta.title }!!
|
||||||
.let { entity ->
|
.let { entity ->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user