2 Commits

Author SHA1 Message Date
hubert 94b61f3de5 We don't need that much RAM 2020-09-30 18:31:17 +02:00
hubert dcc70aab3a Unload bootstraping modules after startup 2020-09-30 18:30:19 +02:00
50 changed files with 438 additions and 1014 deletions
-2
View File
@@ -32,8 +32,6 @@ RUN strip -p --strip-unneeded /myjdk/lib/server/libjvm.so
FROM alpine FROM alpine
RUN apk add --no-cache curl
ENV APPLICATION_USER simplenotes ENV APPLICATION_USER simplenotes
RUN adduser -D -g '' $APPLICATION_USER RUN adduser -D -g '' $APPLICATION_USER
+7 -43
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>parent</artifactId> <artifactId>parent</artifactId>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
@@ -11,7 +9,7 @@
<artifactId>app</artifactId> <artifactId>app</artifactId>
<properties> <properties>
<http4k.version>3.268.0</http4k.version> <http4k.version>3.258.0</http4k.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -38,16 +36,12 @@
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-core</artifactId> <artifactId>http4k-core</artifactId>
<version>${http4k.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-server-jetty</artifactId> <artifactId>http4k-server-jetty</artifactId>
<exclusions> <version>${http4k.version}</version>
<exclusion>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
@@ -56,7 +50,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId> <artifactId>kotlinx-serialization-runtime</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.ocpsoft.prettytime</groupId> <groupId>org.ocpsoft.prettytime</groupId>
@@ -64,21 +58,6 @@
<version>4.0.5.Final</version> <version>4.0.5.Final</version>
</dependency> </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId> <artifactId>shared</artifactId>
@@ -89,32 +68,17 @@
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-testing-hamkrest</artifactId> <artifactId>http4k-testing-hamkrest</artifactId>
<version>${http4k.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.http4k</groupId>
<artifactId>http4k-bom</artifactId>
<version>${http4k.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@@ -1,12 +0,0 @@
package be.simplenotes.app.controllers
import be.simplenotes.persistance.DbHealthCheck
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
class HealthCheckController(private val dbHealthCheck: DbHealthCheck) {
fun healthCheck(request: Request) =
if (dbHealthCheck.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
}
+17 -15
View File
@@ -19,23 +19,25 @@ class AuthFilter(
private val ctx: RequestContexts, private val ctx: RequestContexts,
private val source: JwtSource = JwtSource.Cookie, private val source: JwtSource = JwtSource.Cookie,
private val redirect: Boolean = true, private val redirect: Boolean = true,
) : Filter { ) {
override fun invoke(next: HttpHandler): HttpHandler = { operator fun invoke() = Filter { next ->
val token = when (source) { {
JwtSource.Header -> it.bearerTokenHeader() val token = when (source) {
JwtSource.Cookie -> it.bearerTokenCookie() JwtSource.Header -> it.bearerTokenHeader()
} JwtSource.Cookie -> it.bearerTokenCookie()
val jwtPayload = token?.let { token -> extractor(token) }
when {
jwtPayload != null -> {
ctx[it][authKey] = jwtPayload
next(it)
} }
authType == AuthType.Required -> { val jwtPayload = token?.let { token -> extractor(token) }
if (redirect) Response.redirect("/login") when {
else Response(UNAUTHORIZED) jwtPayload != null -> {
ctx[it][authKey] = jwtPayload
next(it)
}
authType == AuthType.Required -> {
if (redirect) Response.redirect("/login")
else Response(UNAUTHORIZED)
}
else -> next(it)
} }
else -> next(it)
} }
} }
} }
+19 -31
View File
@@ -2,44 +2,32 @@ package be.simplenotes.app.filters
import be.simplenotes.app.extensions.html import be.simplenotes.app.extensions.html
import be.simplenotes.app.views.ErrorView import be.simplenotes.app.views.ErrorView
import be.simplenotes.app.views.ErrorView.Type.*
import org.http4k.core.* import org.http4k.core.*
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.Status.Companion.NOT_IMPLEMENTED
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.sql.SQLTransientException import java.sql.SQLTransientException
class ErrorFilter(private val errorView: ErrorView) : Filter { class ErrorFilter(private val errorView: ErrorView) {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
private fun errorResponse(status: Status): Response { operator fun invoke(): Filter = Filter { next ->
val type = when (status) { {
SERVICE_UNAVAILABLE -> SqlTransientError try {
NOT_FOUND -> NotFound val response = next(it)
NOT_IMPLEMENTED -> Other if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND)
else -> Other .html(errorView.error(ErrorView.Type.NotFound))
} else response
} catch (e: Exception) {
return Response(status).html(errorView.error(type)).noCache() logger.error(e.stackTraceToString())
} if (e is SQLTransientException)
Response(Status.SERVICE_UNAVAILABLE).html(errorView.error(ErrorView.Type.SqlTransientError))
override fun invoke(next: HttpHandler): HttpHandler = { request -> .noCache()
try { else
val response = next(request) Response(Status.INTERNAL_SERVER_ERROR).html(errorView.error(ErrorView.Type.Other)).noCache()
if (response.status == NOT_FOUND) errorResponse(NOT_FOUND) } catch (e: NotImplementedError) {
else response logger.error(e.stackTraceToString())
} catch (e: SQLTransientException) { Response(Status.NOT_IMPLEMENTED).html(errorView.error(ErrorView.Type.Other)).noCache()
logger.error(e.stackTraceToString()) }
errorResponse(SERVICE_UNAVAILABLE)
} catch (e: Exception) {
logger.error(e.stackTraceToString())
errorResponse(INTERNAL_SERVER_ERROR)
} catch (e: NotImplementedError) {
logger.error(e.stackTraceToString())
errorResponse(NOT_IMPLEMENTED)
} }
} }
} }
@@ -2,10 +2,13 @@ package be.simplenotes.app.filters
import org.http4k.core.Filter import org.http4k.core.Filter
import org.http4k.core.HttpHandler import org.http4k.core.HttpHandler
import org.http4k.core.Method
import org.http4k.core.Request import org.http4k.core.Request
object ImmutableFilter : Filter { object ImmutableFilter {
override fun invoke(next: HttpHandler) = { request: Request -> operator fun invoke() = Filter { next: HttpHandler ->
next(request).header("Cache-Control", "public, max-age=31536000, immutable") { request: Request ->
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
}
} }
} }
+11 -9
View File
@@ -4,15 +4,17 @@ import org.http4k.core.Filter
import org.http4k.core.HttpHandler import org.http4k.core.HttpHandler
import org.http4k.core.Request import org.http4k.core.Request
object SecurityFilter : Filter { object SecurityFilter {
override fun invoke(next: HttpHandler): HttpHandler = { request: Request -> operator fun invoke() = Filter { next: HttpHandler ->
val response = next(request) { request: Request ->
.header("X-Content-Type-Options", "nosniff") val response = next(request)
.header("X-Content-Type-Options", "nosniff")
if (response.header("Content-Type")?.contains("text/html") == true) { if (response.header("Content-Type")?.contains("text/html") == true) {
response response
.header("Content-Security-Policy", "default-src 'self'") .header("Content-Security-Policy", "default-src 'self'")
.header("Referrer-Policy", "no-referrer") .header("Referrer-Policy", "no-referrer")
} else response } else response
}
} }
} }
@@ -1,13 +0,0 @@
package be.simplenotes.app.filters
import me.liuwj.ktorm.database.Database
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
class TransactionFilter(private val db: Database) : Filter {
override fun invoke(next: HttpHandler): HttpHandler = { request ->
db.useTransaction {
next(request)
}
}
}
+2 -3
View File
@@ -5,20 +5,19 @@ import be.simplenotes.app.api.ApiUserController
import be.simplenotes.app.filters.AuthFilter import be.simplenotes.app.filters.AuthFilter
import be.simplenotes.app.filters.AuthType import be.simplenotes.app.filters.AuthType
import be.simplenotes.app.filters.JwtSource import be.simplenotes.app.filters.JwtSource
import org.http4k.core.Filter
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
val apiModule = module { val apiModule = module {
single { ApiUserController(get(), get()) } single { ApiUserController(get(), get()) }
single { ApiNoteController(get(), get()) } single { ApiNoteController(get(), get()) }
single<Filter>(named("apiAuthFilter")) { single(named("apiAuthFilter")) {
AuthFilter( AuthFilter(
extractor = get(), extractor = get(),
authType = AuthType.Required, authType = AuthType.Required,
ctx = get(), ctx = get(),
source = JwtSource.Header, source = JwtSource.Header,
redirect = false redirect = false
) )()
} }
} }
+8 -1
View File
@@ -2,9 +2,16 @@ package be.simplenotes.app.modules
import be.simplenotes.app.Config import be.simplenotes.app.Config
import org.koin.dsl.module import org.koin.dsl.module
import org.koin.dsl.onClose
val configModule = module { val configModule = module {
single { Config() } single { Config() } onClose {
println("Unloaded config")
println("Unloaded config")
println("Unloaded config")
println("Unloaded config")
}
single { get<Config>().dataSourceConfig } single { get<Config>().dataSourceConfig }
single { get<Config>().jwtConfig } single { get<Config>().jwtConfig }
single { get<Config>().serverConfig } single { get<Config>().serverConfig }
+4 -2
View File
@@ -1,6 +1,9 @@
package be.simplenotes.app.modules package be.simplenotes.app.modules
import be.simplenotes.app.controllers.* import be.simplenotes.app.controllers.BaseController
import be.simplenotes.app.controllers.NoteController
import be.simplenotes.app.controllers.SettingsController
import be.simplenotes.app.controllers.UserController
import be.simplenotes.app.views.BaseView import be.simplenotes.app.views.BaseView
import be.simplenotes.app.views.NoteView import be.simplenotes.app.views.NoteView
import be.simplenotes.app.views.SettingView import be.simplenotes.app.views.SettingView
@@ -13,7 +16,6 @@ val userModule = module {
} }
val baseModule = module { val baseModule = module {
single { HealthCheckController(get()) }
single { BaseController(get()) } single { BaseController(get()) }
single { BaseView(get()) } single { BaseView(get()) }
} }
+5 -10
View File
@@ -4,14 +4,12 @@ import be.simplenotes.app.Server
import be.simplenotes.app.filters.AuthFilter import be.simplenotes.app.filters.AuthFilter
import be.simplenotes.app.filters.AuthType import be.simplenotes.app.filters.AuthType
import be.simplenotes.app.filters.ErrorFilter import be.simplenotes.app.filters.ErrorFilter
import be.simplenotes.app.filters.TransactionFilter
import be.simplenotes.app.routes.Router import be.simplenotes.app.routes.Router
import be.simplenotes.app.utils.StaticFileResolver import be.simplenotes.app.utils.StaticFileResolver
import be.simplenotes.app.utils.StaticFileResolverImpl import be.simplenotes.app.utils.StaticFileResolverImpl
import be.simplenotes.app.views.ErrorView import be.simplenotes.app.views.ErrorView
import be.simplenotes.shared.config.ServerConfig import be.simplenotes.shared.config.ServerConfig
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
import org.http4k.core.Filter
import org.http4k.core.RequestContexts import org.http4k.core.RequestContexts
import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.RoutingHttpHandler
import org.http4k.server.ConnectorBuilder import org.http4k.server.ConnectorBuilder
@@ -45,19 +43,16 @@ val serverModule = module {
get(), get(),
get(), get(),
get(), get(),
get(),
requiredAuth = get(AuthType.Required.qualifier), requiredAuth = get(AuthType.Required.qualifier),
optionalAuth = get(AuthType.Optional.qualifier), optionalAuth = get(AuthType.Optional.qualifier),
errorFilter = get(named("ErrorFilter")),
apiAuth = get(named("apiAuthFilter")), apiAuth = get(named("apiAuthFilter")),
get(), get()
get(),
get(),
)() )()
} }
single { RequestContexts() } single { RequestContexts() }
single<Filter>(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get()) } single(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() }
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) } single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() }
single { ErrorFilter(get()) } single(named("ErrorFilter")) { ErrorFilter(get())() }
single { TransactionFilter(get()) }
single { ErrorView(get()) } single { ErrorView(get()) }
} }
+28 -36
View File
@@ -2,15 +2,19 @@ package be.simplenotes.app.routes
import be.simplenotes.app.api.ApiNoteController import be.simplenotes.app.api.ApiNoteController
import be.simplenotes.app.api.ApiUserController import be.simplenotes.app.api.ApiUserController
import be.simplenotes.app.controllers.* import be.simplenotes.app.controllers.BaseController
import be.simplenotes.app.filters.* import be.simplenotes.app.controllers.NoteController
import be.simplenotes.app.controllers.SettingsController
import be.simplenotes.app.controllers.UserController
import be.simplenotes.app.filters.ImmutableFilter
import be.simplenotes.app.filters.SecurityFilter
import be.simplenotes.app.filters.jwtPayload
import be.simplenotes.domain.security.JwtPayload import be.simplenotes.domain.security.JwtPayload
import org.http4k.core.* import org.http4k.core.*
import org.http4k.core.Method.* import org.http4k.core.Method.*
import org.http4k.filter.ResponseFilters.GZip import org.http4k.filter.ResponseFilters
import org.http4k.filter.ServerFilters.InitialiseRequestContext import org.http4k.filter.ServerFilters.InitialiseRequestContext
import org.http4k.routing.* import org.http4k.routing.*
import org.http4k.routing.ResourceLoader.Companion.Classpath
class Router( class Router(
private val baseController: BaseController, private val baseController: BaseController,
@@ -19,26 +23,26 @@ class Router(
private val settingsController: SettingsController, private val settingsController: SettingsController,
private val apiUserController: ApiUserController, private val apiUserController: ApiUserController,
private val apiNoteController: ApiNoteController, private val apiNoteController: ApiNoteController,
private val healthCheckController: HealthCheckController,
private val requiredAuth: Filter, private val requiredAuth: Filter,
private val optionalAuth: Filter, private val optionalAuth: Filter,
private val errorFilter: Filter,
private val apiAuth: Filter, private val apiAuth: Filter,
private val errorFilter: ErrorFilter,
private val transactionFilter: TransactionFilter,
private val contexts: RequestContexts, private val contexts: RequestContexts,
) { ) {
operator fun invoke(): RoutingHttpHandler { operator fun invoke(): RoutingHttpHandler {
val basicRoutes = val resourceLoader = ResourceLoader.Classpath(("/static"))
routes( val basicRoutes = routes(
"/health" bind GET to healthCheckController::healthCheck, ImmutableFilter().then(static(resourceLoader, "woff2" to ContentType("font/woff2"))),
ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2"))) )
)
val publicRoutes = routes( infix fun PathMethod.public(handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) }
infix fun PathMethod.protected(handler: ProtectedHandler) = this to { handler(it, it.jwtPayload(contexts)!!) }
val publicRoutes: RoutingHttpHandler = routes(
"/" bind GET public baseController::index, "/" bind GET public baseController::index,
"/register" bind GET public userController::register, "/register" bind GET public userController::register,
"/register" bind POST `public transactional` userController::register, "/register" bind POST public userController::register,
"/login" bind GET public userController::login, "/login" bind GET public userController::login,
"/login" bind POST public userController::login, "/login" bind POST public userController::login,
"/logout" bind POST to userController::logout, "/logout" bind POST to userController::logout,
@@ -47,18 +51,18 @@ class Router(
val protectedRoutes = routes( val protectedRoutes = routes(
"/settings" bind GET protected settingsController::settings, "/settings" bind GET protected settingsController::settings,
"/settings" bind POST transactional settingsController::settings, "/settings" bind POST protected settingsController::settings,
"/export" bind POST protected settingsController::export, "/export" bind POST protected settingsController::export,
"/notes" bind GET protected noteController::list, "/notes" bind GET protected noteController::list,
"/notes" bind POST protected noteController::search, "/notes" bind POST protected noteController::search,
"/notes/new" bind GET protected noteController::new, "/notes/new" bind GET protected noteController::new,
"/notes/new" bind POST transactional noteController::new, "/notes/new" bind POST protected noteController::new,
"/notes/trash" bind GET protected noteController::trash, "/notes/trash" bind GET protected noteController::trash,
"/notes/{uuid}" bind GET protected noteController::note, "/notes/{uuid}" bind GET protected noteController::note,
"/notes/{uuid}" bind POST transactional noteController::note, "/notes/{uuid}" bind POST protected noteController::note,
"/notes/{uuid}/edit" bind GET protected noteController::edit, "/notes/{uuid}/edit" bind GET protected noteController::edit,
"/notes/{uuid}/edit" bind POST transactional noteController::edit, "/notes/{uuid}/edit" bind POST protected noteController::edit,
"/notes/deleted/{uuid}" bind POST transactional noteController::deleted, "/notes/deleted/{uuid}" bind POST protected noteController::deleted,
) )
val apiRoutes = routes( val apiRoutes = routes(
@@ -67,10 +71,10 @@ class Router(
val protectedApiRoutes = routes( val protectedApiRoutes = routes(
"/api/notes" bind GET protected apiNoteController::notes, "/api/notes" bind GET protected apiNoteController::notes,
"/api/notes" bind POST transactional apiNoteController::createNote, "/api/notes" bind POST protected apiNoteController::createNote,
"/api/notes/search" bind POST transactional apiNoteController::search, "/api/notes/search" bind POST protected apiNoteController::search,
"/api/notes/{uuid}" bind GET protected apiNoteController::note, "/api/notes/{uuid}" bind GET protected apiNoteController::note,
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update, "/api/notes/{uuid}" bind PUT protected apiNoteController::update,
) )
val routes = routes( val routes = routes(
@@ -83,23 +87,11 @@ class Router(
val globalFilters = errorFilter val globalFilters = errorFilter
.then(InitialiseRequestContext(contexts)) .then(InitialiseRequestContext(contexts))
.then(SecurityFilter) .then(SecurityFilter())
.then(GZip()) .then(ResponseFilters.GZip())
return globalFilters.then(routes) return globalFilters.then(routes)
} }
private inline infix fun PathMethod.public(crossinline handler: PublicHandler) =
this to { handler(it, it.jwtPayload(contexts)) }
private inline infix fun PathMethod.protected(crossinline handler: ProtectedHandler) =
this to { handler(it, it.jwtPayload(contexts)!!) }
private inline infix fun PathMethod.transactional(crossinline handler: ProtectedHandler) =
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)!!) }
private inline infix fun PathMethod.`public transactional`(crossinline handler: PublicHandler) =
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
} }
private typealias PublicHandler = (Request, JwtPayload?) -> Response private typealias PublicHandler = (Request, JwtPayload?) -> Response
+80 -73
View File
@@ -3,7 +3,8 @@ package be.simplenotes.app.views
import be.simplenotes.app.utils.StaticFileResolver import be.simplenotes.app.utils.StaticFileResolver
import be.simplenotes.domain.security.JwtPayload import be.simplenotes.domain.security.JwtPayload
import kotlinx.html.* import kotlinx.html.*
import kotlinx.html.ThScope.col import kotlinx.html.div
import org.intellij.lang.annotations.Language
class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) { class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
fun renderHome(jwtPayload: JwtPayload?) = renderPage( fun renderHome(jwtPayload: JwtPayload?) = renderPage(
@@ -20,70 +21,74 @@ class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
div("container mx-auto flex flex-wrap justify-center content-center") { div("container mx-auto flex flex-wrap justify-center content-center") {
div("md:order-1 order-2 flipped p-4 my-10 w-full md:w-1/2") { unsafe {
attributes["aria-label"] = "demo" @Language("html")
div("flex justify-between mb-4") { val html =
h1("text-2xl underline") { +"Notes" } """
span { <div aria-label="demo" class="md:order-1 order-2 flipped p-4 my-10 w-full md:w-1/2">
span("btn btn-teal pointer-events-none") { +"Trash (3)" } <div class="flex justify-between mb-4">
span("ml-2 btn btn-green pointer-events-none") { +"New" } <h1 class="text-2xl underline">Notes</h1>
} <span>
} <span class="btn btn-teal pointer-events-none">Trash (3)</span>
form(classes = "md:space-x-2") { <span class="ml-2 btn btn-green pointer-events-none">New</span>
id = "search" </span>
input { </div>
attributes["aria-label"] = "demo-search" <form class="md:space-x-2" id="search">
attributes["name"] = "search" <input aria-label="demo-search" name="search" disabled="" value="tag:&quot;demo&quot;">
attributes["disabled"] = "" <span id="buttons">
attributes["value"] = "tag:\"demo\"" <button type="button" disabled="" class="btn btn-green pointer-events-none">search</button>
} <span class="btn btn-red pointer-events-none">clear</span>
span { </span>
id = "buttons" </form>
button(type = ButtonType.button, classes = "btn btn-green pointer-events-none") { <div class="overflow-x-auto">
attributes["disabled"] = "" <table id="notes">
+"search" <thead>
} <tr>
span("btn btn-red pointer-events-none") { +"clear" } <th scope="col" class="w-1/2">Title</th>
} <th scope="col" class="w-1/4">Updated</th>
} <th scope="col" class="w-1/4">Tags</th>
div("overflow-x-auto") { </tr>
demoTable() </thead>
} <tbody>
} <tr>
welcome() <td><span class="text-blue-200 font-semibold underline">Formula 1</span></td>
} <td class="text-center">moments ago</td>
} <td>
<ul class="inline flex flex-wrap justify-center">
<li class="mx-2 my-1"><span class="tag disabled">#demo</span ></li>
</ul>
</td>
</tr>
<tr>
<td><span class="text-blue-200 font-semibold underline">Syntax highlighting</span></td>
<td class="text-center">2 hours ago</td>
<td>
<ul class="inline flex flex-wrap justify-center">
<li class="mx-2 my-1"><span class="tag disabled">#features</span></li>
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
</ul>
</td>
</tr>
<tr>
<td><span class="text-blue-200 font-semibold underline">report</span></td>
<td class="text-center">5 days ago</td>
<td>
<ul class="inline flex flex-wrap justify-center">
<li class="mx-2 my-1"><span class="tag disabled">#study</span></li>
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
""".trimIndent()
@Suppress("NOTHING_TO_INLINE") +html
private inline fun DIV.demoTable() {
table {
id = "notes"
thead {
tr {
th(scope = col, classes = "w-1/2") { +"Title" }
th(scope = col, classes = "w-1/4") { +"Updated" }
th(scope = col, classes = "w-1/4") { +"Tags" }
}
}
tbody {
listOf(
Triple("Formula 1", "moments ago", arrayOf("#demo")),
Triple("Syntax highlighting", "2 hours ago", arrayOf("#features", "#demo")),
Triple("report", "5 days ago", arrayOf("#study", "#demo")),
).forEach { (title, ago, tags) ->
tr {
td { span("text-blue-200 font-semibold underline") { +title } }
td("text-center") { +ago }
td {
ul("inline flex flex-wrap justify-center") {
tags.forEach { tag ->
li("mx-2 my-1") { span("tag disabled") { +tag } }
}
}
}
}
}
} }
welcome()
} }
} }
@@ -91,16 +96,18 @@ class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
private inline fun DIV.welcome() { private inline fun DIV.welcome() {
div("w-full my-auto md:w-1/2 md:order-2 order-1 text-center") { div("w-full my-auto md:w-1/2 md:order-2 order-1 text-center") {
div("m-4 rounded-lg p-6") { div("m-4 rounded-lg p-6") {
h2("text-3xl text-teal-400 underline") { +"Features:" } p("text-teal-400") {
ul("list-disc text-lg list-inside") { h2("text-3xl text-teal-400 underline") { +"Features:" }
li { +"Markdown support" } ul("list-disc text-lg list-inside") {
li { +"Full text search" } li { +"Markdown support" }
li { +"Structured search" } li { +"Full text search" }
li { +"Code highlighting" } li { +"Structured search" }
li { +"Fast and lightweight" } li { +"Code highlighting" }
li { +"No tracking" } li { +"Fast and lightweight" }
li { +"Works without javascript" } li { +"No tracking" }
li { +"Data export" } li { +"Works without javascript" }
li { +"Data export" }
}
} }
} }
} }
+1 -12
View File
@@ -31,7 +31,7 @@ abstract class View(staticFileResolver: StaticFileResolver) {
attributes["crossorigin"] = "anonymous" attributes["crossorigin"] = "anonymous"
} }
link(rel = "stylesheet", href = styles) link(rel = "stylesheet", href = styles)
icons() link(rel = "shortcut icon", href = "/favicon.ico", type = "image/x-icon")
scripts.forEach { src -> scripts.forEach { src ->
script(src = src) {} script(src = src) {}
} }
@@ -42,15 +42,4 @@ abstract class View(staticFileResolver: StaticFileResolver) {
} }
} }
} }
@Suppress("NOTHING_TO_INLINE")
private inline fun HEAD.icons() {
link(rel = "apple-touch-icon", href = "/apple-touch-icon.png") { attributes["sizes"] = "180x180" }
link(rel = "icon", href = "/favicon-32x32.png", type = "image/png") { attributes["sizes"] = "32x32" }
link(rel = "icon", href = "/favicon-16x16.png", type = "image/png") { attributes["sizes"] = "16x16" }
link(rel = "manifest", href = "/site.webmanifest")
link(rel = "mask-icon", href = "/safari-pinned-tab.svg") { attributes["color"] = "#2c7a7b" }
meta(name = "msapplication-TileColor", content = "#00aba9")
meta(name = "theme-color", content = "#2c7a7b")
}
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#00aba9</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,33 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1255 6993 c-179 -23 -313 -62 -461 -133 -396 -187 -665 -533 -766
-981 l-23 -104 0 -2275 0 -2275 23 -102 c125 -565 530 -970 1095 -1095 l102
-23 2275 0 2275 0 102 23 c565 125 970 530 1095 1095 l23 102 0 2275 0 2275
-23 102 c-125 566 -521 964 -1090 1095 l-97 22 -2250 2 c-1237 1 -2263 -1
-2280 -3z m1024 -1979 c128 -18 287 -70 394 -127 262 -139 448 -395 472 -649
3 -34 8 -78 11 -95 l5 -33 -255 0 -254 0 -6 28 c-2 15 -7 44 -11 65 -27 168
-204 335 -416 393 -94 26 -317 24 -421 -4 -218 -59 -345 -196 -356 -384 -6
-105 16 -173 80 -243 81 -88 227 -148 563 -230 509 -123 742 -228 916 -412
123 -131 170 -248 176 -448 9 -245 -55 -420 -212 -580 -162 -165 -387 -270
-667 -311 -154 -22 -461 -15 -595 15 -280 62 -513 193 -662 373 -72 85 -162
262 -185 358 -8 36 -18 95 -22 133 l-6 67 254 0 255 0 6 -53 c22 -179 135
-332 310 -417 127 -61 195 -74 387 -75 195 0 279 16 399 76 179 89 260 234
231 415 -22 137 -98 231 -243 302 -109 55 -202 84 -447 142 -237 57 -306 76
-427 120 -340 125 -535 303 -600 550 -23 87 -23 271 0 360 87 335 409 595 827
665 108 19 371 18 499 -1z m3170 0 c202 -35 325 -95 453 -224 77 -77 93 -100
141 -201 30 -63 63 -143 72 -179 49 -183 48 -159 52 -1307 l4 -1083 -256 0
-255 0 0 1034 c0 1160 1 1133 -70 1282 -92 192 -259 271 -548 262 -117 -4
-149 -9 -221 -33 -177 -60 -340 -200 -440 -376 l-31 -56 0 -1056 0 -1057 -255
0 -255 0 0 1480 0 1480 239 0 238 0 6 -82 c4 -46 7 -120 7 -165 0 -45 3 -88 6
-97 4 -11 28 8 92 72 163 164 356 267 575 307 103 19 334 18 446 -1z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,19 +0,0 @@
{
"name": "SimpleNotes",
"short_name": "SimpleNotes",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
@@ -27,8 +27,8 @@ internal class AuthFilterTest {
private val simpleJwt = SimpleJwt(jwtConfig) private val simpleJwt = SimpleJwt(jwtConfig)
private val extractor = JwtPayloadExtractor(simpleJwt) private val extractor = JwtPayloadExtractor(simpleJwt)
private val ctx = RequestContexts() private val ctx = RequestContexts()
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx) private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)()
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx) private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)()
private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) } private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) }
+1 -5
View File
@@ -11,11 +11,7 @@ simplenotes.be {
import strict-transport import strict-transport
header -Server header -Server
reverse_proxy http://localhost:8080 { reverse_proxy http://localhost:8080
health_path /health
health_interval 5s
health_timeout 200ms
}
} }
dev.simplenotes.be { dev.simplenotes.be {
+2 -10
View File
@@ -19,10 +19,8 @@ services:
volumes: volumes:
- notes-db-volume:/var/lib/mysql - notes-db-volume:/var/lib/mysql
healthcheck: healthcheck:
test: "mysql --protocol=tcp -u simplenotes -p$MYSQL_PASSWORD -e 'show databases'" test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
interval: 5s timeout: 10s
timeout: 1s
start_period: 2s
retries: 10 retries: 10
simplenotes: simplenotes:
@@ -41,12 +39,6 @@ services:
# - PASSWORD # - PASSWORD
ports: ports:
- 127.0.0.1:8080:8080 - 127.0.0.1:8080:8080
healthcheck:
test: "curl --fail -s http://localhost:8080/health"
interval: 5s
timeout: 1s
start_period: 2s
retries: 3
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
+2 -28
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>parent</artifactId> <artifactId>parent</artifactId>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
@@ -24,30 +22,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
</dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>io.konform</groupId> <groupId>io.konform</groupId>
<artifactId>konform-jvm</artifactId> <artifactId>konform-jvm</artifactId>
@@ -85,7 +59,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId> <artifactId>kotlinx-serialization-runtime</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
+3 -18
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>parent</artifactId> <artifactId>parent</artifactId>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
@@ -11,10 +9,6 @@
<artifactId>persistance</artifactId> <artifactId>persistance</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency> <dependency>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
<artifactId>domain</artifactId> <artifactId>domain</artifactId>
@@ -33,17 +27,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mariadb.jdbc</groupId> <groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
@@ -67,10 +50,12 @@
<dependency> <dependency>
<groupId>me.liuwj.ktorm</groupId> <groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId> <artifactId>ktorm-core</artifactId>
<version>3.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>me.liuwj.ktorm</groupId> <groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-support-mysql</artifactId> <artifactId>ktorm-support-mysql</artifactId>
<version>3.0.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -1,24 +1,18 @@
package be.simplenotes.persistance package be.simplenotes.persistance
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import be.simplenotes.shared.config.DataSourceConfig import be.simplenotes.shared.config.DataSourceConfig
import org.flywaydb.core.Flyway import org.flywaydb.core.Flyway
import javax.sql.DataSource import javax.sql.DataSource
interface DbMigrations {
fun migrate()
}
internal class DbMigrationsImpl( internal class DbMigrationsImpl(
private val dataSource: DataSource, private val dataSource: DataSource,
private val dataSourceConfig: DataSourceConfig, private val dataSourceConfig: DataSourceConfig
) : DbMigrations { ) : DbMigrations {
override fun migrate() { override fun migrate() {
val migrationDir = when (dataSourceConfig.type()) { val migrationDir = when {
DbType.H2 -> "db/migration/other" dataSourceConfig.jdbcUrl.contains("mariadb") -> "db/migration/mariadb"
DbType.MariaDb -> "db/migration/mariadb" else -> "db/migration/other"
} }
Flyway.configure() Flyway.configure()
@@ -1,28 +0,0 @@
package be.simplenotes.persistance
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import be.simplenotes.shared.config.DataSourceConfig
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.database.asIterable
import java.sql.SQLTransientException
interface DbHealthCheck {
fun isOk(): Boolean
}
internal class DbHealthCheckImpl(
private val db: Database,
private val dataSourceConfig: DataSourceConfig,
) : DbHealthCheck {
override fun isOk() = if (dataSourceConfig.type() == DbType.H2) true
else try {
db.useConnection { connection ->
connection.prepareStatement("""SHOW DATABASES""").use {
it.executeQuery().asIterable().map { it.getString(1) }
}
}.any { it in dataSourceConfig.jdbcUrl }
} catch (e: SQLTransientException) {
false
}
}
@@ -2,10 +2,6 @@ package be.simplenotes.persistance
import be.simplenotes.domain.usecases.repositories.NoteRepository import be.simplenotes.domain.usecases.repositories.NoteRepository
import be.simplenotes.domain.usecases.repositories.UserRepository import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.converters.NoteConverterImpl
import be.simplenotes.persistance.converters.UserConverter
import be.simplenotes.persistance.converters.UserConverterImpl
import be.simplenotes.persistance.notes.NoteRepositoryImpl import be.simplenotes.persistance.notes.NoteRepositoryImpl
import be.simplenotes.persistance.users.UserRepositoryImpl import be.simplenotes.persistance.users.UserRepositoryImpl
import be.simplenotes.shared.config.DataSourceConfig import be.simplenotes.shared.config.DataSourceConfig
@@ -15,9 +11,12 @@ import me.liuwj.ktorm.database.Database
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
import org.koin.dsl.onClose import org.koin.dsl.onClose
import org.mapstruct.factory.Mappers
import javax.sql.DataSource import javax.sql.DataSource
interface DbMigrations {
fun migrate()
}
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource { private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
val hikariConfig = HikariConfig().also { val hikariConfig = HikariConfig().also {
it.jdbcUrl = conf.jdbcUrl it.jdbcUrl = conf.jdbcUrl
@@ -35,14 +34,11 @@ val migrationModule = module {
} }
val persistanceModule = module { val persistanceModule = module {
single<NoteConverter> { NoteConverterImpl() } single<UserRepository> { UserRepositoryImpl(get()) }
single<UserConverter> { UserConverterImpl() } single<NoteRepository> { NoteRepositoryImpl(get()) }
single<UserRepository> { UserRepositoryImpl(get(), get()) }
single<NoteRepository> { NoteRepositoryImpl(get(), get()) }
single { hikariDataSource(get()) } bind DataSource::class onClose { it?.close() } single { hikariDataSource(get()) } bind DataSource::class onClose { it?.close() }
single { single {
get<DbMigrations>().migrate() get<DbMigrations>().migrate()
Database.connect(get<DataSource>()) Database.connect(get<DataSource>())
} }
single<DbHealthCheck> { DbHealthCheckImpl(get(), get()) }
} }
@@ -1,80 +0,0 @@
package be.simplenotes.persistance.converters
import be.simplenotes.domain.model.*
import be.simplenotes.persistance.notes.NoteEntity
import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper
import org.mapstruct.Mapping
import org.mapstruct.Mappings
import java.time.LocalDateTime
import java.util.*
/**
* 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])
internal abstract class NoteConverter {
fun toNote(entity: NoteEntity, tags: Tags) =
Note(NoteMetadata(title = entity.title, tags = tags), entity.markdown, entity.html)
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toNoteMetadata(entity: NoteEntity, tags: Tags): NoteMetadata
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toPersistedNoteMetadata(entity: NoteEntity, tags: Tags): PersistedNoteMetadata
fun toPersistedNote(entity: NoteEntity, tags: Tags) = PersistedNote(
NoteMetadata(title = entity.title, tags = tags),
entity.markdown, entity.html, entity.updatedAt, entity.uuid, entity.public
)
@Mappings(
Mapping(target = ".", source = "entity"),
Mapping(target = "trash", source = "entity.deleted"),
Mapping(target = "tags", source = "tags"),
)
abstract fun toExportedNote(entity: NoteEntity, tags: Tags): ExportedNote
fun toEntity(note: Note, uuid: UUID, userId: Int, updatedAt: LocalDateTime) = NoteEntity {
this.title = note.meta.title
this.markdown = note.markdown
this.html = note.html
this.uuid = uuid
this.deleted = false
this.public = false
this.user.id = userId
this.updatedAt = updatedAt
}
@Mappings(
Mapping(target = ".", source = "note"),
Mapping(target = "updatedAt", source = "updatedAt"),
Mapping(target = "uuid", source = "uuid"),
Mapping(target = "public", constant = "false"),
)
abstract fun toPersistedNote(note: Note, updatedAt: LocalDateTime, uuid: UUID): PersistedNote
abstract fun toEntity(persistedNoteMetadata: PersistedNoteMetadata): NoteEntity
abstract fun toEntity(noteMetadata: NoteMetadata): NoteEntity
@Mapping(target = "title", source = "meta.title")
abstract fun toEntity(persistedNote: PersistedNote): NoteEntity
@Mapping(target = "deleted", source = "trash")
abstract fun toEntity(exportedNote: ExportedNote): NoteEntity
}
typealias Tags = List<String>
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
@@ -1,16 +0,0 @@
package be.simplenotes.persistance.converters
import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User
import be.simplenotes.persistance.users.UserEntity
import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper
@Mapper(uses = [UserEntityFactory::class])
internal interface UserConverter {
fun convertToUser(userEntity: UserEntity): User
fun convertToPersistedUser(userEntity: UserEntity): PersistedUser
fun convertToEntity(user: User): UserEntity
}
internal class UserEntityFactory : Entity.Factory<UserEntity>()
@@ -1,11 +1,7 @@
package be.simplenotes.persistance.notes package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.ExportedNote import be.simplenotes.domain.model.*
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.domain.usecases.repositories.NoteRepository import be.simplenotes.domain.usecases.repositories.NoteRepository
import be.simplenotes.persistance.converters.NoteConverter
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.entity.*
@@ -13,7 +9,7 @@ import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
internal class NoteRepositoryImpl(private val db: Database, private val converter: NoteConverter) : NoteRepository { internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
override fun findAll( override fun findAll(
@@ -21,7 +17,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
limit: Int, limit: Int,
offset: Int, offset: Int,
tag: String?, tag: String?,
deleted: Boolean, deleted: Boolean
): List<PersistedNoteMetadata> { ): List<PersistedNoteMetadata> {
require(limit > 0) { "limit should be positive" } require(limit > 0) { "limit should be positive" }
require(offset >= 0) { "offset should not be negative" } require(offset >= 0) { "offset should not be negative" }
@@ -50,7 +46,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
return notes.map { note -> return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList() val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toPersistedNoteMetadata(note, tags) note.toPersistedMetadata(tags)
} }
} }
@@ -60,17 +56,22 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
override fun create(userId: Int, note: Note): PersistedNote { override fun create(userId: Int, note: Note): PersistedNote {
val uuid = UUID.randomUUID() val uuid = UUID.randomUUID()
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now()) val entity = note.toEntity(uuid, userId).apply {
db.notes.add(entity) this.updatedAt = LocalDateTime.now()
db.batchInsert(Tags) { }
note.meta.tags.forEach { tagName -> db.useTransaction {
item { db.notes.add(entity)
it.noteUuid to uuid db.batchInsert(Tags) {
it.name to tagName note.meta.tags.forEach { tagName ->
item {
it.noteUuid to uuid
it.name to tagName
}
} }
} }
} }
return converter.toPersistedNote(entity, note.meta.tags)
return entity.toPersistedNote(note.meta.tags)
} }
override fun find(userId: Int, uuid: UUID): PersistedNote? { override fun find(userId: Int, uuid: UUID): PersistedNote? {
@@ -85,53 +86,68 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
.where { Tags.noteUuid eq uuid } .where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! } .map { it[Tags.name]!! }
return converter.toPersistedNote(note, tags) return note.toPersistedNote(tags)
} }
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? { override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
val now = LocalDateTime.now() db.useTransaction {
val count = db.update(Notes) {
it.title to note.meta.title
it.markdown to note.markdown
it.html to note.html
it.updatedAt to now
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
}
if (count == 0) return null val now = LocalDateTime.now()
val count = db.update(Notes) {
// delete all tags it.title to note.meta.title
db.delete(Tags) { it.markdown to note.markdown
it.noteUuid eq uuid it.html to note.html
} it.updatedAt to now
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
// put new ones
note.meta.tags.forEach { tagName ->
db.insert(Tags) {
it.name to tagName
it.noteUuid to uuid
} }
}
return converter.toPersistedNote(note, now, uuid) if (count == 0) return null
// delete all tags
db.delete(Tags) {
it.noteUuid eq uuid
}
// put new ones
note.meta.tags.forEach { tagName ->
db.insert(Tags) {
it.name to tagName
it.noteUuid to uuid
}
}
return PersistedNote(
meta = note.meta,
markdown = note.markdown,
html = note.html,
updatedAt = now,
uuid = uuid,
public = false, // TODO
)
}
} }
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean { override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
return if (!permanent) { return if (!permanent) {
db.update(Notes) { db.useTransaction {
it.deleted to true db.update(Notes) {
it.updatedAt to LocalDateTime.now() it.deleted to true
where { it.userId eq userId and (it.uuid eq uuid) } it.updatedAt to LocalDateTime.now()
where { it.userId eq userId and (it.uuid eq uuid) }
}
} == 1 } == 1
} else } else db.useTransaction {
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
}
} }
override fun restore(userId: Int, uuid: UUID): Boolean { override fun restore(userId: Int, uuid: UUID): Boolean {
return db.update(Notes) { return db.useTransaction {
it.deleted to false db.update(Notes) {
where { (it.userId eq userId) and (it.uuid eq uuid) } it.deleted to false
} == 1 where { (it.userId eq userId) and (it.uuid eq uuid) }
} == 1
}
} }
override fun getTags(userId: Int): List<String> = override fun getTags(userId: Int): List<String> =
@@ -159,8 +175,14 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
val tagsByUuid = notes.tagsByUuid() val tagsByUuid = notes.tagsByUuid()
return notes.map { note -> return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList() ExportedNote(
converter.toExportedNote(note, tags) title = note.title,
tags = tagsByUuid[note.uuid] ?: emptyList(),
markdown = note.markdown,
html = note.html,
updatedAt = note.updatedAt,
trash = note.deleted,
)
} }
} }
@@ -174,7 +196,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
return notes.map { note -> return notes.map { note ->
val tags = tagsByUuid[note.uuid] ?: emptyList() val tags = tagsByUuid[note.uuid] ?: emptyList()
converter.toPersistedNote(note, tags) note.toPersistedNote(tags)
} }
} }
@@ -201,7 +223,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
.where { Tags.noteUuid eq uuid } .where { Tags.noteUuid eq uuid }
.map { it[Tags.name]!! } .map { it[Tags.name]!! }
return converter.toPersistedNote(note, tags) return note.toPersistedNote(tags)
} }
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> { private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
+24 -3
View File
@@ -2,12 +2,15 @@
package be.simplenotes.persistance.notes package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.NoteMetadata
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.persistance.extensions.uuidBinary import be.simplenotes.persistance.extensions.uuidBinary
import be.simplenotes.persistance.users.UserEntity import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.persistance.users.Users import be.simplenotes.persistance.users.Users
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.Entity import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.entity.sequenceOf
import me.liuwj.ktorm.schema.* import me.liuwj.ktorm.schema.*
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
@@ -43,3 +46,21 @@ internal interface NoteEntity : Entity<NoteEntity> {
var user: UserEntity var user: UserEntity
} }
internal fun NoteEntity.toPersistedMetadata(tags: List<String>) = PersistedNoteMetadata(title, tags, updatedAt, uuid)
internal fun NoteEntity.toPersistedNote(tags: List<String>) =
PersistedNote(NoteMetadata(title, tags), markdown, html, updatedAt, uuid, public)
internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity {
val note = this
return NoteEntity {
this.title = note.meta.title
this.markdown = note.markdown
this.html = note.html
this.uuid = uuid
this.deleted = false
this.public = false
this.user["id"] = userId
}
}
@@ -3,35 +3,30 @@ package be.simplenotes.persistance.users
import be.simplenotes.domain.model.PersistedUser import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User import be.simplenotes.domain.model.User
import be.simplenotes.domain.usecases.repositories.UserRepository import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.converters.UserConverter import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.any import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.entity.find
import java.sql.SQLIntegrityConstraintViolationException import java.sql.SQLIntegrityConstraintViolationException
internal class UserRepositoryImpl(private val db: Database, private val converter: UserConverter) : UserRepository { internal class UserRepositoryImpl(private val db: Database) : UserRepository {
override fun create(user: User): PersistedUser? { override fun create(user: User): PersistedUser? {
return try { return try {
val id = db.insertAndGenerateKey(Users) { db.useTransaction {
it.username to user.username val id = db.insertAndGenerateKey(Users) {
it.password to user.password it.username to user.username
} as Int it.password to user.password
PersistedUser(user.username, user.password, id) } as Int
PersistedUser(user.username, user.password, id)
}
} catch (e: SQLIntegrityConstraintViolationException) { } catch (e: SQLIntegrityConstraintViolationException) {
null null
} }
} }
override fun find(username: String) = db.users.find { it.username eq username } override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser()
?.let { converter.convertToPersistedUser(it) } override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
override fun find(id: Int) = db.users.find { it.id eq id }?.let {
converter.convertToPersistedUser(it)
}
override fun exists(username: String) = db.users.any { it.username eq username } override fun exists(username: String) = db.users.any { it.username eq username }
override fun exists(id: Int) = db.users.any { it.id eq id } override fun exists(id: Int) = db.users.any { it.id eq id }
override fun delete(id: Int) = db.delete(Users) { it.id eq id } == 1 override fun delete(id: Int) = db.useTransaction { db.delete(Users) { it.id eq id } == 1 }
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! } override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
} }
+7 -7
View File
@@ -1,11 +1,9 @@
package be.simplenotes.persistance.users package be.simplenotes.persistance.users
import me.liuwj.ktorm.database.Database import be.simplenotes.domain.model.PersistedUser
import me.liuwj.ktorm.entity.Entity import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.entity.sequenceOf import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.schema.Table import me.liuwj.ktorm.schema.*
import me.liuwj.ktorm.schema.int
import me.liuwj.ktorm.schema.varchar
internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) { internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
companion object : Users(null) companion object : Users(null)
@@ -20,9 +18,11 @@ internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
internal interface UserEntity : Entity<UserEntity> { internal interface UserEntity : Entity<UserEntity> {
companion object : Entity.Factory<UserEntity>() companion object : Entity.Factory<UserEntity>()
var id: Int val id: Int
var username: String var username: String
var password: String var password: String
} }
internal fun UserEntity.toPersistedUser() = PersistedUser(username, password, id)
internal val Database.users get() = this.sequenceOf(Users, withReferences = false) internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
@@ -1,8 +0,0 @@
package be.simplenotes.persistance.utils
import be.simplenotes.shared.config.DataSourceConfig
enum class DbType { H2, MariaDb }
fun DataSourceConfig.type(): DbType = if (jdbcUrl.contains("mariadb")) DbType.MariaDb
else DbType.H2
@@ -1,168 +0,0 @@
package be.simplenotes.persistance.converters
import be.simplenotes.domain.model.*
import be.simplenotes.persistance.notes.NoteEntity
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 {
@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"
html = "html"
}
val tags = listOf("a", "b")
val note = converter.toNote(entity, tags)
val expectedNote = Note(NoteMetadata(
title = "title",
tags = tags,
), markdown = "md", html = "html")
assertThat(note).isEqualTo(expectedNote)
}
@Test
fun `convert NoteEntity to ExportedNote`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity {
title = "title"
markdown = "md"
html = "html"
updatedAt = LocalDateTime.MIN
deleted = true
}
val tags = listOf("a", "b")
val note = converter.toExportedNote(entity, tags)
val expectedNote = ExportedNote(
title = "title",
tags = tags,
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
trash = true
)
assertThat(note).isEqualTo(expectedNote)
}
@Test
fun `convert NoteEntity to PersistedNoteMetadata`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity {
uuid = UUID.randomUUID()
title = "title"
markdown = "md"
html = "html"
updatedAt = LocalDateTime.MIN
deleted = true
}
val tags = listOf("a", "b")
val note = converter.toPersistedNoteMetadata(entity, tags)
val expectedNote = PersistedNoteMetadata(
title = "title",
tags = tags,
updatedAt = LocalDateTime.MIN,
uuid = entity.uuid
)
assertThat(note).isEqualTo(expectedNote)
}
}
@Nested
@DisplayName("Models -> Entity")
inner class ModelsToEntity {
@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)
assertThat(entity)
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("html", "html")
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("uuid", entity.uuid)
.hasFieldOrPropertyWithValue("updatedAt", LocalDateTime.MIN)
}
@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)
assertThat(entity)
.hasFieldOrPropertyWithValue("uuid", persistedNoteMetadata.uuid)
.hasFieldOrPropertyWithValue("updatedAt", persistedNoteMetadata.updatedAt)
.hasFieldOrPropertyWithValue("title", "title")
}
@Test
fun `convert NoteMetadata to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val noteMetadata = NoteMetadata("title", emptyList())
val entity = converter.toEntity(noteMetadata)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
}
@Test
fun `convert PersistedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val persistedNote = PersistedNote(
NoteMetadata("title", emptyList()),
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
uuid = UUID.randomUUID(),
public = true
)
val entity = converter.toEntity(persistedNote)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("uuid", persistedNote.uuid)
.hasFieldOrPropertyWithValue("updatedAt", persistedNote.updatedAt)
.hasFieldOrPropertyWithValue("deleted", false)
.hasFieldOrPropertyWithValue("public", true)
}
@Test
fun `convert ExportedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val exportedNote = ExportedNote(
"title",
emptyList(),
markdown = "md",
html = "html",
updatedAt = LocalDateTime.MIN,
trash = true
)
val entity = converter.toEntity(exportedNote)
assertThat(entity)
.hasFieldOrPropertyWithValue("title", "title")
.hasFieldOrPropertyWithValue("markdown", "md")
.hasFieldOrPropertyWithValue("updatedAt", exportedNote.updatedAt)
.hasFieldOrPropertyWithValue("deleted", true)
}
}
}
@@ -1,48 +0,0 @@
package be.simplenotes.persistance.converters
import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User
import be.simplenotes.persistance.users.UserEntity
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mapstruct.factory.Mappers
internal class UserConverterTest {
@Test
fun `convert UserEntity to User`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity {
username = "test"
password = "test2"
}.apply {
this["id"] = 2
}
val user = converter.convertToUser(entity)
assertThat(user).isEqualTo(User("test", "test2"))
}
@Test
fun `convert UserEntity to PersistedUser`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity {
username = "test"
password = "test2"
}.apply {
this["id"] = 2
}
val user = converter.convertToPersistedUser(entity)
assertThat(user).isEqualTo(PersistedUser("test", "test2", 2))
}
@Test
fun `convert User to UserEntity`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val user = User("test", "test2")
val entity = converter.convertToEntity(user)
assertThat(entity)
.hasFieldOrPropertyWithValue("username", "test")
.hasFieldOrPropertyWithValue("password", "test2")
}
}
@@ -4,16 +4,12 @@ import be.simplenotes.domain.model.*
import be.simplenotes.domain.usecases.repositories.NoteRepository import be.simplenotes.domain.usecases.repositories.NoteRepository
import be.simplenotes.domain.usecases.repositories.UserRepository import be.simplenotes.domain.usecases.repositories.UserRepository
import be.simplenotes.persistance.DbMigrations import be.simplenotes.persistance.DbMigrations
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.migrationModule import be.simplenotes.persistance.migrationModule
import be.simplenotes.persistance.persistanceModule import be.simplenotes.persistance.persistanceModule
import be.simplenotes.shared.config.DataSourceConfig import be.simplenotes.shared.config.DataSourceConfig
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.eq import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.filter import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.entity.find
import me.liuwj.ktorm.entity.mapColumns
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.flywaydb.core.Flyway import org.flywaydb.core.Flyway
@@ -21,7 +17,6 @@ import org.junit.jupiter.api.*
import org.junit.jupiter.api.parallel.ResourceLock import org.junit.jupiter.api.parallel.ResourceLock
import org.koin.dsl.koinApplication import org.koin.dsl.koinApplication
import org.koin.dsl.module import org.koin.dsl.module
import org.mapstruct.factory.Mappers
import java.sql.SQLIntegrityConstraintViolationException import java.sql.SQLIntegrityConstraintViolationException
import java.util.* import java.util.*
import javax.sql.DataSource import javax.sql.DataSource
@@ -191,12 +186,10 @@ internal class NoteRepositoryImplTest {
fun `find an existing note`() { fun `find an existing note`() {
createNote(user1.id, "1", listOf("a", "b")) createNote(user1.id, "1", listOf("a", "b"))
val converter = Mappers.getMapper(NoteConverter::class.java)
val note = db.notes.find { it.title eq "1" }!! val note = db.notes.find { it.title eq "1" }!!
.let { entity -> .let { entity ->
val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List<String> val tags = db.tags.filter { it.noteUuid eq entity.uuid }.mapColumns { it.name } as List<String>
converter.toPersistedNote(entity, tags) entity.toPersistedNote(tags)
} }
assertThat(noteRepo.find(user1.id, note.uuid)) assertThat(noteRepo.find(user1.id, note.uuid))
+106 -134
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
@@ -19,7 +17,7 @@
<properties> <properties>
<java.version>14</java.version> <java.version>14</java.version>
<kotlin.version>1.4.10</kotlin.version> <kotlin.version>1.4.0</kotlin.version>
<junit.version>5.6.2</junit.version> <junit.version>5.6.2</junit.version>
<kotlin.code.style>official</kotlin.code.style> <kotlin.code.style>official</kotlin.code.style>
@@ -29,8 +27,6 @@
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.target>${java.version}</maven.compiler.target>
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget> <kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -39,83 +35,101 @@
<artifactId>kotlin-stdlib-jdk8</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version> <version>${kotlin.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>0.10.5</version>
</dependency>
<!-- region tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-test</artifactId>
<version>2.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<version>1.7.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
<!-- endregion -->
</dependencies> </dependencies>
<build> <build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit-platform</artifactId>
<version>3.0.0-M5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version> <version>3.0.0-M4</version>
<executions> <dependencies>
<execution> <dependency>
<id>enforce</id> <groupId>org.apache.maven.surefire</groupId>
<goals> <artifactId>surefire-junit-platform</artifactId>
<goal>enforce</goal> <version>3.0.0-M4</version>
</goals> </dependency>
<configuration> </dependencies>
<rules> </plugin>
<banDuplicatePomDependencyVersions/> <plugin>
<requireMavenVersion> <groupId>org.apache.maven.plugins</groupId>
<version>3.6</version> <artifactId>maven-compiler-plugin</artifactId>
</requireMavenVersion> <version>3.8.1</version>
</rules> </plugin>
</configuration> <plugin>
</execution> <groupId>org.apache.maven.plugins</groupId>
</executions> <artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>kotlin-maven-plugin</artifactId> <artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version> <version>${kotlin.version}</version>
<executions> <executions>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
<execution> <execution>
<id>compile</id> <id>compile</id>
<phase>process-sources</phase> <phase>process-sources</phase>
@@ -151,81 +165,39 @@
</plugins> </plugins>
</build> </build>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-bom</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk7</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version> <version>${kotlin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId> <artifactId>kotlinx-serialization-runtime</artifactId>
<version>1.0.0</version> <version>1.0-M1-1.4.0-rc</version>
</dependency> </dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-support-mysql</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- region tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<version>1.7.0.3</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
</dependency>
<!-- endregion -->
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
-16
View File
@@ -37,22 +37,6 @@
<version>${lucene.version}</version> <version>${lucene.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId> <artifactId>shared</artifactId>
+2 -16
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>parent</artifactId> <artifactId>parent</artifactId>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
@@ -10,24 +8,12 @@
<artifactId>shared</artifactId> <artifactId>shared</artifactId>
<dependencies>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>