Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32337ec308 | |||
| 681fd635b3 | |||
| 9467db2382 | |||
| 7ed3494808 |
@@ -32,6 +32,8 @@ 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
|
||||||
|
|
||||||
|
|||||||
+39
-4
@@ -11,7 +11,7 @@
|
|||||||
<artifactId>app</artifactId>
|
<artifactId>app</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<http4k.version>3.258.0</http4k.version>
|
<http4k.version>3.268.0</http4k.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -38,12 +38,16 @@
|
|||||||
<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>
|
||||||
<version>${http4k.version}</version>
|
<exclusions>
|
||||||
|
<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>
|
||||||
@@ -60,6 +64,21 @@
|
|||||||
<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>
|
||||||
@@ -70,11 +89,27 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -19,9 +19,8 @@ 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 {
|
||||||
operator fun invoke() = Filter { next ->
|
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||||
{
|
|
||||||
val token = when (source) {
|
val token = when (source) {
|
||||||
JwtSource.Header -> it.bearerTokenHeader()
|
JwtSource.Header -> it.bearerTokenHeader()
|
||||||
JwtSource.Cookie -> it.bearerTokenCookie()
|
JwtSource.Cookie -> it.bearerTokenCookie()
|
||||||
@@ -39,7 +38,6 @@ class AuthFilter(
|
|||||||
else -> next(it)
|
else -> next(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
||||||
|
|||||||
@@ -2,32 +2,44 @@ 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) {
|
class ErrorFilter(private val errorView: ErrorView) : Filter {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
operator fun invoke(): Filter = Filter { next ->
|
private fun errorResponse(status: Status): Response {
|
||||||
{
|
val type = when (status) {
|
||||||
|
SERVICE_UNAVAILABLE -> SqlTransientError
|
||||||
|
NOT_FOUND -> NotFound
|
||||||
|
NOT_IMPLEMENTED -> Other
|
||||||
|
else -> Other
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(status).html(errorView.error(type)).noCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke(next: HttpHandler): HttpHandler = { request ->
|
||||||
try {
|
try {
|
||||||
val response = next(it)
|
val response = next(request)
|
||||||
if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND)
|
if (response.status == NOT_FOUND) errorResponse(NOT_FOUND)
|
||||||
.html(errorView.error(ErrorView.Type.NotFound))
|
|
||||||
else response
|
else response
|
||||||
|
} catch (e: SQLTransientException) {
|
||||||
|
logger.error(e.stackTraceToString())
|
||||||
|
errorResponse(SERVICE_UNAVAILABLE)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error(e.stackTraceToString())
|
logger.error(e.stackTraceToString())
|
||||||
if (e is SQLTransientException)
|
errorResponse(INTERNAL_SERVER_ERROR)
|
||||||
Response(Status.SERVICE_UNAVAILABLE).html(errorView.error(ErrorView.Type.SqlTransientError))
|
|
||||||
.noCache()
|
|
||||||
else
|
|
||||||
Response(Status.INTERNAL_SERVER_ERROR).html(errorView.error(ErrorView.Type.Other)).noCache()
|
|
||||||
} catch (e: NotImplementedError) {
|
} catch (e: NotImplementedError) {
|
||||||
logger.error(e.stackTraceToString())
|
logger.error(e.stackTraceToString())
|
||||||
Response(Status.NOT_IMPLEMENTED).html(errorView.error(ErrorView.Type.Other)).noCache()
|
errorResponse(NOT_IMPLEMENTED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ 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 {
|
object ImmutableFilter : Filter {
|
||||||
operator fun invoke() = Filter { next: HttpHandler ->
|
override fun invoke(next: HttpHandler) = { request: Request ->
|
||||||
{ request: Request ->
|
|
||||||
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ 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 {
|
object SecurityFilter : Filter {
|
||||||
operator fun invoke() = Filter { next: HttpHandler ->
|
override fun invoke(next: HttpHandler): HttpHandler = { request: Request ->
|
||||||
{ request: Request ->
|
|
||||||
val response = next(request)
|
val response = next(request)
|
||||||
.header("X-Content-Type-Options", "nosniff")
|
.header("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
@@ -16,5 +15,4 @@ object SecurityFilter {
|
|||||||
.header("Referrer-Policy", "no-referrer")
|
.header("Referrer-Policy", "no-referrer")
|
||||||
} else response
|
} else response
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,19 +5,20 @@ 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(named("apiAuthFilter")) {
|
single<Filter>(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
|
||||||
)()
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package be.simplenotes.app.modules
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
import be.simplenotes.app.controllers.BaseController
|
import be.simplenotes.app.controllers.*
|
||||||
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
|
||||||
@@ -16,6 +13,7 @@ val userModule = module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val baseModule = module {
|
val baseModule = module {
|
||||||
|
single { HealthCheckController(get()) }
|
||||||
single { BaseController(get()) }
|
single { BaseController(get()) }
|
||||||
single { BaseView(get()) }
|
single { BaseView(get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ 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
|
||||||
@@ -43,16 +45,19 @@ 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(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() }
|
single<Filter>(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get()) }
|
||||||
single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() }
|
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
|
||||||
single(named("ErrorFilter")) { ErrorFilter(get())() }
|
single { ErrorFilter(get()) }
|
||||||
|
single { TransactionFilter(get()) }
|
||||||
single { ErrorView(get()) }
|
single { ErrorView(get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,15 @@ 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.BaseController
|
import be.simplenotes.app.controllers.*
|
||||||
import be.simplenotes.app.controllers.NoteController
|
import be.simplenotes.app.filters.*
|
||||||
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
|
import org.http4k.filter.ResponseFilters.GZip
|
||||||
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,
|
||||||
@@ -23,26 +19,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 resourceLoader = ResourceLoader.Classpath(("/static"))
|
val basicRoutes =
|
||||||
val basicRoutes = routes(
|
routes(
|
||||||
ImmutableFilter().then(static(resourceLoader, "woff2" to ContentType("font/woff2"))),
|
"/health" bind GET to healthCheckController::healthCheck,
|
||||||
|
ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2")))
|
||||||
)
|
)
|
||||||
|
|
||||||
infix fun PathMethod.public(handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) }
|
val publicRoutes = routes(
|
||||||
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 userController::register,
|
"/register" bind POST `public transactional` 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,
|
||||||
@@ -51,18 +47,18 @@ class Router(
|
|||||||
|
|
||||||
val protectedRoutes = routes(
|
val protectedRoutes = routes(
|
||||||
"/settings" bind GET protected settingsController::settings,
|
"/settings" bind GET protected settingsController::settings,
|
||||||
"/settings" bind POST protected settingsController::settings,
|
"/settings" bind POST transactional 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 protected noteController::new,
|
"/notes/new" bind POST transactional 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 protected noteController::note,
|
"/notes/{uuid}" bind POST transactional noteController::note,
|
||||||
"/notes/{uuid}/edit" bind GET protected noteController::edit,
|
"/notes/{uuid}/edit" bind GET protected noteController::edit,
|
||||||
"/notes/{uuid}/edit" bind POST protected noteController::edit,
|
"/notes/{uuid}/edit" bind POST transactional noteController::edit,
|
||||||
"/notes/deleted/{uuid}" bind POST protected noteController::deleted,
|
"/notes/deleted/{uuid}" bind POST transactional noteController::deleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
val apiRoutes = routes(
|
val apiRoutes = routes(
|
||||||
@@ -71,10 +67,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 protected apiNoteController::createNote,
|
"/api/notes" bind POST transactional apiNoteController::createNote,
|
||||||
"/api/notes/search" bind POST protected apiNoteController::search,
|
"/api/notes/search" bind POST transactional apiNoteController::search,
|
||||||
"/api/notes/{uuid}" bind GET protected apiNoteController::note,
|
"/api/notes/{uuid}" bind GET protected apiNoteController::note,
|
||||||
"/api/notes/{uuid}" bind PUT protected apiNoteController::update,
|
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update,
|
||||||
)
|
)
|
||||||
|
|
||||||
val routes = routes(
|
val routes = routes(
|
||||||
@@ -87,11 +83,23 @@ class Router(
|
|||||||
|
|
||||||
val globalFilters = errorFilter
|
val globalFilters = errorFilter
|
||||||
.then(InitialiseRequestContext(contexts))
|
.then(InitialiseRequestContext(contexts))
|
||||||
.then(SecurityFilter())
|
.then(SecurityFilter)
|
||||||
.then(ResponseFilters.GZip())
|
.then(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
|
||||||
|
|||||||
@@ -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()) }
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -11,7 +11,11 @@ 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 {
|
||||||
|
|||||||
+10
-2
@@ -19,8 +19,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- notes-db-volume:/var/lib/mysql
|
- notes-db-volume:/var/lib/mysql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
test: "mysql --protocol=tcp -u simplenotes -p$MYSQL_PASSWORD -e 'show databases'"
|
||||||
timeout: 10s
|
interval: 5s
|
||||||
|
timeout: 1s
|
||||||
|
start_period: 2s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
simplenotes:
|
simplenotes:
|
||||||
@@ -39,6 +41,12 @@ 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
|
||||||
|
|||||||
@@ -24,6 +24,30 @@
|
|||||||
<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>
|
||||||
|
|||||||
+15
-2
@@ -11,6 +11,10 @@
|
|||||||
<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>
|
||||||
@@ -29,6 +33,17 @@
|
|||||||
<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>
|
||||||
@@ -52,12 +67,10 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-4
@@ -1,18 +1,24 @@
|
|||||||
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 {
|
val migrationDir = when (dataSourceConfig.type()) {
|
||||||
dataSourceConfig.jdbcUrl.contains("mariadb") -> "db/migration/mariadb"
|
DbType.H2 -> "db/migration/other"
|
||||||
else -> "db/migration/other"
|
DbType.MariaDb -> "db/migration/mariadb"
|
||||||
}
|
}
|
||||||
|
|
||||||
Flyway.configure()
|
Flyway.configure()
|
||||||
@@ -2,6 +2,10 @@ 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
|
||||||
@@ -11,12 +15,9 @@ 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
|
||||||
@@ -34,11 +35,14 @@ val migrationModule = module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val persistanceModule = module {
|
val persistanceModule = module {
|
||||||
single<UserRepository> { UserRepositoryImpl(get()) }
|
single<NoteConverter> { NoteConverterImpl() }
|
||||||
single<NoteRepository> { NoteRepositoryImpl(get()) }
|
single<UserConverter> { UserConverterImpl() }
|
||||||
|
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()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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>()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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,7 +1,11 @@
|
|||||||
package be.simplenotes.persistance.notes
|
package be.simplenotes.persistance.notes
|
||||||
|
|
||||||
import be.simplenotes.domain.model.*
|
import be.simplenotes.domain.model.ExportedNote
|
||||||
|
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.*
|
||||||
@@ -9,7 +13,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) : NoteRepository {
|
internal class NoteRepositoryImpl(private val db: Database, private val converter: NoteConverter) : NoteRepository {
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
override fun findAll(
|
override fun findAll(
|
||||||
@@ -17,7 +21,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
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" }
|
||||||
@@ -46,7 +50,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
|
|
||||||
return notes.map { note ->
|
return notes.map { note ->
|
||||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||||
note.toPersistedMetadata(tags)
|
converter.toPersistedNoteMetadata(note, tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +60,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
|
|
||||||
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 = note.toEntity(uuid, userId).apply {
|
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now())
|
||||||
this.updatedAt = LocalDateTime.now()
|
|
||||||
}
|
|
||||||
db.useTransaction {
|
|
||||||
db.notes.add(entity)
|
db.notes.add(entity)
|
||||||
db.batchInsert(Tags) {
|
db.batchInsert(Tags) {
|
||||||
note.meta.tags.forEach { tagName ->
|
note.meta.tags.forEach { tagName ->
|
||||||
@@ -69,9 +70,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
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? {
|
||||||
@@ -86,12 +85,10 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
.where { Tags.noteUuid eq uuid }
|
.where { Tags.noteUuid eq uuid }
|
||||||
.map { it[Tags.name]!! }
|
.map { it[Tags.name]!! }
|
||||||
|
|
||||||
return note.toPersistedNote(tags)
|
return converter.toPersistedNote(note, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
|
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
|
||||||
db.useTransaction {
|
|
||||||
|
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val count = db.update(Notes) {
|
val count = db.update(Notes) {
|
||||||
it.title to note.meta.title
|
it.title to note.meta.title
|
||||||
@@ -116,39 +113,26 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PersistedNote(
|
return converter.toPersistedNote(note, now, uuid)
|
||||||
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.useTransaction {
|
|
||||||
db.update(Notes) {
|
db.update(Notes) {
|
||||||
it.deleted to true
|
it.deleted to true
|
||||||
it.updatedAt to LocalDateTime.now()
|
it.updatedAt to LocalDateTime.now()
|
||||||
where { it.userId eq userId and (it.uuid eq uuid) }
|
where { it.userId eq userId and (it.uuid eq uuid) }
|
||||||
}
|
|
||||||
} == 1
|
} == 1
|
||||||
} else db.useTransaction {
|
} else
|
||||||
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.useTransaction {
|
return db.update(Notes) {
|
||||||
db.update(Notes) {
|
|
||||||
it.deleted to false
|
it.deleted to false
|
||||||
where { (it.userId eq userId) and (it.uuid eq uuid) }
|
where { (it.userId eq userId) and (it.uuid eq uuid) }
|
||||||
} == 1
|
} == 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTags(userId: Int): List<String> =
|
override fun getTags(userId: Int): List<String> =
|
||||||
db.from(Tags)
|
db.from(Tags)
|
||||||
@@ -175,14 +159,8 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
val tagsByUuid = notes.tagsByUuid()
|
val tagsByUuid = notes.tagsByUuid()
|
||||||
|
|
||||||
return notes.map { note ->
|
return notes.map { note ->
|
||||||
ExportedNote(
|
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||||
title = note.title,
|
converter.toExportedNote(note, tags)
|
||||||
tags = tagsByUuid[note.uuid] ?: emptyList(),
|
|
||||||
markdown = note.markdown,
|
|
||||||
html = note.html,
|
|
||||||
updatedAt = note.updatedAt,
|
|
||||||
trash = note.deleted,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +174,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
|
|
||||||
return notes.map { note ->
|
return notes.map { note ->
|
||||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||||
note.toPersistedNote(tags)
|
converter.toPersistedNote(note, tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +201,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
|||||||
.where { Tags.noteUuid eq uuid }
|
.where { Tags.noteUuid eq uuid }
|
||||||
.map { it[Tags.name]!! }
|
.map { it[Tags.name]!! }
|
||||||
|
|
||||||
return note.toPersistedNote(tags)
|
return converter.toPersistedNote(note, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
|
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
|
|
||||||
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.*
|
import me.liuwj.ktorm.database.Database
|
||||||
import me.liuwj.ktorm.entity.*
|
import me.liuwj.ktorm.entity.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.*
|
||||||
@@ -46,21 +43,3 @@ 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,30 +3,35 @@ 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 me.liuwj.ktorm.database.*
|
import be.simplenotes.persistance.converters.UserConverter
|
||||||
|
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.any
|
||||||
|
import me.liuwj.ktorm.entity.find
|
||||||
import java.sql.SQLIntegrityConstraintViolationException
|
import java.sql.SQLIntegrityConstraintViolationException
|
||||||
|
|
||||||
internal class UserRepositoryImpl(private val db: Database) : UserRepository {
|
internal class UserRepositoryImpl(private val db: Database, private val converter: UserConverter) : UserRepository {
|
||||||
override fun create(user: User): PersistedUser? {
|
override fun create(user: User): PersistedUser? {
|
||||||
return try {
|
return try {
|
||||||
db.useTransaction {
|
|
||||||
val id = db.insertAndGenerateKey(Users) {
|
val id = db.insertAndGenerateKey(Users) {
|
||||||
it.username to user.username
|
it.username to user.username
|
||||||
it.password to user.password
|
it.password to user.password
|
||||||
} as Int
|
} as Int
|
||||||
PersistedUser(user.username, user.password, id)
|
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 }?.toPersistedUser()
|
override fun find(username: String) = db.users.find { it.username eq username }
|
||||||
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
|
?.let { converter.convertToPersistedUser(it) }
|
||||||
|
|
||||||
|
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.useTransaction { db.delete(Users) { it.id eq id } == 1 }
|
override fun delete(id: Int) = 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]!! }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package be.simplenotes.persistance.users
|
package be.simplenotes.persistance.users
|
||||||
|
|
||||||
import be.simplenotes.domain.model.PersistedUser
|
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.Table
|
||||||
|
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)
|
||||||
@@ -18,11 +20,9 @@ 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>()
|
||||||
|
|
||||||
val id: Int
|
var 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)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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,12 +4,16 @@ 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.*
|
import me.liuwj.ktorm.database.Database
|
||||||
import me.liuwj.ktorm.dsl.*
|
import me.liuwj.ktorm.dsl.eq
|
||||||
import me.liuwj.ktorm.entity.*
|
import me.liuwj.ktorm.entity.filter
|
||||||
|
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
|
||||||
@@ -17,6 +21,7 @@ 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
|
||||||
@@ -186,10 +191,12 @@ 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>
|
||||||
entity.toPersistedNote(tags)
|
converter.toPersistedNote(entity, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(noteRepo.find(user1.id, note.uuid))
|
assertThat(noteRepo.find(user1.id, note.uuid))
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
<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>
|
||||||
@@ -37,55 +39,6 @@
|
|||||||
<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>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>
|
||||||
@@ -148,6 +101,21 @@
|
|||||||
<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>
|
||||||
@@ -187,34 +155,77 @@
|
|||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
<artifactId>kotlin-bom</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-json-jvm</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.0</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>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,22 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -10,6 +10,19 @@
|
|||||||
|
|
||||||
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user