diff --git a/app/src/Server.kt b/app/src/Server.kt index 0b4cb1d..c6b3c0b 100644 --- a/app/src/Server.kt +++ b/app/src/Server.kt @@ -1,10 +1,10 @@ package be.simplenotes.app +import jakarta.annotation.PostConstruct +import jakarta.annotation.PreDestroy +import jakarta.inject.Singleton import org.http4k.server.Http4kServer import org.slf4j.LoggerFactory -import javax.annotation.PostConstruct -import javax.annotation.PreDestroy -import javax.inject.Singleton import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig @Singleton diff --git a/app/src/api/ApiNoteController.kt b/app/src/api/ApiNoteController.kt index a2fabd6..68121bd 100644 --- a/app/src/api/ApiNoteController.kt +++ b/app/src/api/ApiNoteController.kt @@ -16,7 +16,7 @@ import org.http4k.core.Status.Companion.OK import org.http4k.lens.Path import org.http4k.lens.uuid import java.util.* -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class ApiNoteController( @@ -28,7 +28,7 @@ class ApiNoteController( val content = noteContentLens(request) return noteService.create(loggedInUser, content).fold( { Response(BAD_REQUEST) }, - { uuidContentLens(UuidContent(it.uuid), Response(OK)) } + { uuidContentLens(UuidContent(it.uuid), Response(OK)) }, ) } @@ -51,7 +51,7 @@ class ApiNoteController( { if (it == null) Response(NOT_FOUND) else Response(OK) - } + }, ) } diff --git a/app/src/api/ApiUserController.kt b/app/src/api/ApiUserController.kt index bf1ab3a..12aec03 100644 --- a/app/src/api/ApiUserController.kt +++ b/app/src/api/ApiUserController.kt @@ -9,7 +9,7 @@ import org.http4k.core.Request import org.http4k.core.Response import org.http4k.core.Status.Companion.BAD_REQUEST import org.http4k.core.Status.Companion.OK -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class ApiUserController( @@ -23,7 +23,7 @@ class ApiUserController( .login(loginFormLens(request)) .fold( { Response(BAD_REQUEST) }, - { tokenLens(Token(it), Response(OK)) } + { tokenLens(Token(it), Response(OK)) }, ) } diff --git a/app/src/controllers/BaseController.kt b/app/src/controllers/BaseController.kt index 13dbb84..565fb8d 100644 --- a/app/src/controllers/BaseController.kt +++ b/app/src/controllers/BaseController.kt @@ -6,7 +6,7 @@ import be.simplenotes.views.BaseView import org.http4k.core.Request import org.http4k.core.Response import org.http4k.core.Status.Companion.OK -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class BaseController(private val view: BaseView) { diff --git a/app/src/controllers/NoteController.kt b/app/src/controllers/NoteController.kt index d43cd5d..b3abea3 100644 --- a/app/src/controllers/NoteController.kt +++ b/app/src/controllers/NoteController.kt @@ -15,7 +15,7 @@ import org.http4k.core.Status.Companion.OK import org.http4k.core.body.form import org.http4k.routing.path import java.util.* -import javax.inject.Singleton +import jakarta.inject.Singleton import kotlin.math.abs @Singleton @@ -35,24 +35,24 @@ class NoteController( MarkdownParsingError.MissingMeta -> view.noteEditor( loggedInUser, error = "Missing note metadata", - textarea = markdownForm + textarea = markdownForm, ) MarkdownParsingError.InvalidMeta -> view.noteEditor( loggedInUser, error = "Invalid note metadata", - textarea = markdownForm + textarea = markdownForm, ) is MarkdownParsingError.ValidationError -> view.noteEditor( loggedInUser, validationErrors = it.validationErrors, - textarea = markdownForm + textarea = markdownForm, ) } Response(BAD_REQUEST).html(html) }, { Response.redirect("/notes/${it.uuid}") - } + }, ) } @@ -114,24 +114,24 @@ class NoteController( MarkdownParsingError.MissingMeta -> view.noteEditor( loggedInUser, error = "Missing note metadata", - textarea = markdownForm + textarea = markdownForm, ) MarkdownParsingError.InvalidMeta -> view.noteEditor( loggedInUser, error = "Invalid note metadata", - textarea = markdownForm + textarea = markdownForm, ) is MarkdownParsingError.ValidationError -> view.noteEditor( loggedInUser, validationErrors = it.validationErrors, - textarea = markdownForm + textarea = markdownForm, ) } Response(BAD_REQUEST).html(html) }, { Response.redirect("/notes/${note.uuid}") - } + }, ) } diff --git a/app/src/controllers/SettingsController.kt b/app/src/controllers/SettingsController.kt index 63f5567..7fb67cf 100644 --- a/app/src/controllers/SettingsController.kt +++ b/app/src/controllers/SettingsController.kt @@ -11,7 +11,7 @@ import be.simplenotes.views.SettingView import org.http4k.core.* import org.http4k.core.body.form import org.http4k.core.cookie.invalidateCookie -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class SettingsController( @@ -33,20 +33,20 @@ class SettingsController( DeleteError.WrongPassword -> Response(Status.OK).html( settingView.settings( loggedInUser, - error = "Wrong password" - ) + error = "Wrong password", + ), ) is DeleteError.InvalidForm -> Response(Status.OK).html( settingView.settings( loggedInUser, - validationErrors = it.validationErrors - ) + validationErrors = it.validationErrors, + ), ) } }, { Response.redirect("/").invalidateCookie("Bearer") - } + }, ) } @@ -73,7 +73,7 @@ class SettingsController( .body(exportService.exportAsJson(loggedInUser.userId)) } else Response(Status.OK).body(exportService.exportAsJson(loggedInUser.userId)).header( "Content-Type", - "application/json" + "application/json", ) } diff --git a/app/src/controllers/UserController.kt b/app/src/controllers/UserController.kt index a780e17..881ec12 100644 --- a/app/src/controllers/UserController.kt +++ b/app/src/controllers/UserController.kt @@ -17,7 +17,7 @@ import org.http4k.core.cookie.SameSite import org.http4k.core.cookie.cookie import org.http4k.core.cookie.invalidateCookie import java.util.concurrent.TimeUnit -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class UserController( @@ -27,7 +27,7 @@ class UserController( ) { fun register(request: Request, loggedInUser: LoggedInUser?): Response { if (request.method == GET) return Response(OK).html( - userView.register(loggedInUser) + userView.register(loggedInUser), ) val result = userService.register(request.registerForm()) @@ -37,19 +37,19 @@ class UserController( val html = when (it) { RegisterError.UserExists -> userView.register( loggedInUser, - error = "User already exists" + error = "User already exists", ) is RegisterError.InvalidRegisterForm -> userView.register( loggedInUser, - validationErrors = it.validationErrors + validationErrors = it.validationErrors, ) } Response(OK).html(html) }, { Response.redirect("/login") - } + }, ) } @@ -58,7 +58,7 @@ class UserController( fun login(request: Request, loggedInUser: LoggedInUser?): Response { if (request.method == GET) return Response(OK).html( - userView.login(loggedInUser) + userView.login(loggedInUser), ) val result = userService.login(request.loginForm()) @@ -69,24 +69,24 @@ class UserController( LoginError.Unregistered -> userView.login( loggedInUser, - error = "User does not exist" + error = "User does not exist", ) LoginError.WrongPassword -> userView.login( loggedInUser, - error = "Wrong password" + error = "Wrong password", ) is LoginError.InvalidLoginForm -> userView.login( loggedInUser, - validationErrors = it.validationErrors + validationErrors = it.validationErrors, ) } Response(OK).html(html) }, { token -> Response.redirect("/notes").loginCookie(token, request.isSecure()) - } + }, ) } @@ -101,8 +101,8 @@ class UserController( httpOnly = true, sameSite = SameSite.Lax, maxAge = validityInSeconds, - secure = secure - ) + secure = secure, + ), ) } diff --git a/app/src/extensions/Http4kExtensions.kt b/app/src/extensions/Http4kExtensions.kt index 7c70770..060f780 100644 --- a/app/src/extensions/Http4kExtensions.kt +++ b/app/src/extensions/Http4kExtensions.kt @@ -24,13 +24,13 @@ fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false val bodyLens = httpBodyRoot( listOf(Meta(true, "body", ParamMeta.ObjectParam, "body")), ContentType.APPLICATION_JSON.withNoDirectives(), - ContentNegotiation.StrictNoDirective + ContentNegotiation.StrictNoDirective, ).map( { it.payload.asString() }, - { Body(it) } + { Body(it) }, ) inline fun Json.auto(): BiDiBodyLensSpec = bodyLens.map( { decodeFromString(it) }, - { encodeToString(it) } + { encodeToString(it) }, ) diff --git a/app/src/filters/ErrorFilter.kt b/app/src/filters/ErrorFilter.kt index 174b9cf..7afc0c5 100644 --- a/app/src/filters/ErrorFilter.kt +++ b/app/src/filters/ErrorFilter.kt @@ -10,7 +10,7 @@ import org.http4k.core.Status.Companion.NOT_IMPLEMENTED import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE import org.slf4j.LoggerFactory import java.sql.SQLTransientException -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class ErrorFilter(private val errorView: ErrorView) : Filter { diff --git a/app/src/jetty/Jetty.kt b/app/src/jetty/Jetty.kt index 5b91387..dac3869 100644 --- a/app/src/jetty/Jetty.kt +++ b/app/src/jetty/Jetty.kt @@ -8,14 +8,14 @@ import org.eclipse.jetty.servlet.ServletHolder import org.http4k.core.HttpHandler import org.http4k.server.Http4kServer import org.http4k.server.ServerConfig -import org.http4k.servlet.asServlet +import org.http4k.servlet.jakarta.asServlet class Jetty(private val port: Int, private val server: Server) : ServerConfig { constructor(port: Int, vararg inConnectors: ConnectorBuilder) : this( port, Server().apply { inConnectors.forEach { addConnector(it(this)) } - } + }, ) override fun toServer(http: HttpHandler): Http4kServer { diff --git a/app/src/modules/AuthModule.kt b/app/src/modules/AuthModule.kt index a9b8aed..02c47f2 100644 --- a/app/src/modules/AuthModule.kt +++ b/app/src/modules/AuthModule.kt @@ -7,8 +7,8 @@ import io.micronaut.context.annotation.Factory import io.micronaut.context.annotation.Primary import org.http4k.core.RequestContexts import org.http4k.lens.RequestContextKey -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Factory class AuthModule { @@ -39,7 +39,7 @@ class AuthModule { simpleJwt = simpleJwt, lens = lens, source = JwtSource.Header, - redirect = false + redirect = false, ) @Singleton diff --git a/app/src/modules/JsonModule.kt b/app/src/modules/JsonModule.kt index f140148..73536cd 100644 --- a/app/src/modules/JsonModule.kt +++ b/app/src/modules/JsonModule.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import java.time.LocalDateTime import java.util.* -import javax.inject.Singleton +import jakarta.inject.Singleton @Factory class JsonModule { diff --git a/app/src/modules/ServerModule.kt b/app/src/modules/ServerModule.kt index c1d59a3..9819d6f 100644 --- a/app/src/modules/ServerModule.kt +++ b/app/src/modules/ServerModule.kt @@ -9,8 +9,8 @@ import io.micronaut.context.annotation.Factory import org.eclipse.jetty.server.ServerConnector import org.http4k.server.Http4kServer import org.http4k.server.asServer -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton import org.eclipse.jetty.server.Server as JettyServer import org.http4k.server.ServerConfig as Http4kServerConfig diff --git a/app/src/routes/ApiRoutes.kt b/app/src/routes/ApiRoutes.kt index 60e8ab5..ba5d6db 100644 --- a/app/src/routes/ApiRoutes.kt +++ b/app/src/routes/ApiRoutes.kt @@ -12,8 +12,8 @@ import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.bind import org.http4k.routing.routes import java.util.function.Supplier -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class ApiRoutes( @@ -23,7 +23,6 @@ class ApiRoutes( @Named("required") private val authLens: RequiredAuthLens, ) : Supplier { override fun get(): RoutingHttpHandler { - infix fun PathMethod.to(action: ProtectedHandler) = this to { req: Request -> action(req, authLens(req)) } @@ -38,9 +37,9 @@ class ApiRoutes( "/search" bind POST to ::search, "/{uuid}" bind GET to ::note, "/{uuid}" bind PUT to ::update, - ) + ), ).withBasePath("/notes") - } + }, ).withBasePath("/api") } diff --git a/app/src/routes/BasicRoutes.kt b/app/src/routes/BasicRoutes.kt index cb9041a..f52f96c 100644 --- a/app/src/routes/BasicRoutes.kt +++ b/app/src/routes/BasicRoutes.kt @@ -13,8 +13,8 @@ import org.http4k.core.Request import org.http4k.core.then import org.http4k.routing.* import java.util.function.Supplier -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class BasicRoutes( @@ -26,7 +26,6 @@ class BasicRoutes( ) : Supplier { override fun get(): RoutingHttpHandler { - infix fun PathMethod.to(action: PublicHandler) = this to { req: Request -> action(req, authLens(req)) } @@ -34,8 +33,8 @@ class BasicRoutes( static( ResourceLoader.Classpath("/static"), "woff2" to ContentType("font/woff2"), - "webmanifest" to ContentType("application/manifest+json") - ) + "webmanifest" to ContentType("application/manifest+json"), + ), ) return routes( @@ -48,9 +47,9 @@ class BasicRoutes( "/login" bind POST to userCtrl::login, "/logout" bind POST to userCtrl::logout, "/notes/public/{uuid}" bind GET to noteCtrl::public, - ) + ), ), - staticHandler + staticHandler, ) } } diff --git a/app/src/routes/NoteRoutes.kt b/app/src/routes/NoteRoutes.kt index 83da7f9..c67e286 100644 --- a/app/src/routes/NoteRoutes.kt +++ b/app/src/routes/NoteRoutes.kt @@ -12,8 +12,8 @@ import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.bind import org.http4k.routing.routes import java.util.function.Supplier -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class NoteRoutes( @@ -22,7 +22,6 @@ class NoteRoutes( @Named("required") private val authLens: RequiredAuthLens, ) : Supplier { override fun get(): RoutingHttpHandler { - infix fun PathMethod.to(action: ProtectedHandler) = this to { req: Request -> action(req, authLens(req)) } @@ -40,7 +39,7 @@ class NoteRoutes( "/{uuid}/edit" bind POST to ::edit, "/deleted/{uuid}" bind POST to ::deleted, ).withBasePath("/notes") - } + }, ) } } diff --git a/app/src/routes/Router.kt b/app/src/routes/Router.kt index 4f6731c..e063e1b 100644 --- a/app/src/routes/Router.kt +++ b/app/src/routes/Router.kt @@ -9,7 +9,7 @@ import org.http4k.filter.ServerFilters.InitialiseRequestContext import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.routes import java.util.function.Supplier -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class Router( @@ -18,9 +18,8 @@ class Router( private val subRouters: List>, ) { operator fun invoke(): RoutingHttpHandler { - val routes = routes( - *subRouters.map { it.get() }.toTypedArray() + *subRouters.map { it.get() }.toTypedArray(), ) return errorFilter diff --git a/app/src/routes/SettingsRoutes.kt b/app/src/routes/SettingsRoutes.kt index b1a569d..cde0e9d 100644 --- a/app/src/routes/SettingsRoutes.kt +++ b/app/src/routes/SettingsRoutes.kt @@ -12,8 +12,8 @@ import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.bind import org.http4k.routing.routes import java.util.function.Supplier -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class SettingsRoutes( @@ -22,7 +22,6 @@ class SettingsRoutes( @Named("required") private val authLens: RequiredAuthLens, ) : Supplier { override fun get(): RoutingHttpHandler { - infix fun PathMethod.to(action: ProtectedHandler) = this to { req: Request -> action(req, authLens(req)) } @@ -31,7 +30,7 @@ class SettingsRoutes( "/settings" bind GET to settingsController::settings, "/settings" bind POST to settingsController::settings, "/export" bind POST to settingsController::export, - ) + ), ) } } diff --git a/app/src/utils/StaticFilesResolver.kt b/app/src/utils/StaticFilesResolver.kt index 1d0d8a2..87a838d 100644 --- a/app/src/utils/StaticFilesResolver.kt +++ b/app/src/utils/StaticFilesResolver.kt @@ -3,7 +3,7 @@ package be.simplenotes.app.utils import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import javax.inject.Singleton +import jakarta.inject.Singleton interface StaticFileResolver { fun resolve(name: String): String? diff --git a/app/test/filters/RequiredAuthFilterTest.kt b/app/test/filters/RequiredAuthFilterTest.kt index e035026..c2e01cb 100644 --- a/app/test/filters/RequiredAuthFilterTest.kt +++ b/app/test/filters/RequiredAuthFilterTest.kt @@ -58,8 +58,8 @@ internal class RequiredAuthFilterTest { }, "/protected" bind GET to requiredAuth.then { request: Request -> Response(OK).body(requiredLens(request).toString()) - } - ) + }, + ), ) // endregion diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2bb0367..4de511c 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,6 +10,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21") implementation("org.jetbrains.kotlin:kotlin-serialization:1.8.21") implementation("com.github.johnrengelman:shadow:8.1.1") - implementation("org.jlleitschuh.gradle:ktlint-gradle:11.3.2") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.13.0") implementation("com.github.ben-manes:gradle-versions-plugin:0.46.0") } diff --git a/buildSrc/src/main/kotlin/be/simplenotes/Libs.kt b/buildSrc/src/main/kotlin/be/simplenotes/Libs.kt index b62fa6e..67dfcae 100644 --- a/buildSrc/src/main/kotlin/be/simplenotes/Libs.kt +++ b/buildSrc/src/main/kotlin/be/simplenotes/Libs.kt @@ -5,7 +5,7 @@ package be.simplenotes object Libs { object Micronaut { - private const val version = "2.5.1" + private const val version = "4.0.0-M2" const val inject = "io.micronaut:micronaut-inject:$version" const val processor = "io.micronaut:micronaut-inject-java:$version" } diff --git a/buildSrc/src/main/kotlin/be/simplenotes/app-shadow.gradle.kts b/buildSrc/src/main/kotlin/be/simplenotes/app-shadow.gradle.kts index eb1d340..f702343 100644 --- a/buildSrc/src/main/kotlin/be/simplenotes/app-shadow.gradle.kts +++ b/buildSrc/src/main/kotlin/be/simplenotes/app-shadow.gradle.kts @@ -11,6 +11,10 @@ tasks.withType { archiveAppendix.set("with-dependencies") manifest.attributes["Main-Class"] = "be.simplenotes.app.SimpleNotesKt" + // johnrengelman/shadow#449 + // we need this for lucene-core + manifest.attributes["Multi-Release"] = "true" + mergeServiceFiles() File(rootProject.projectDir, "buildSrc/src/main/resources/exclusions") diff --git a/buildSrc/src/main/kotlin/be/simplenotes/base.gradle.kts b/buildSrc/src/main/kotlin/be/simplenotes/base.gradle.kts index 0b2c1b8..ae4f267 100644 --- a/buildSrc/src/main/kotlin/be/simplenotes/base.gradle.kts +++ b/buildSrc/src/main/kotlin/be/simplenotes/base.gradle.kts @@ -4,5 +4,11 @@ plugins { id("be.simplenotes.java-convention") id("be.simplenotes.kotlin-convention") id("be.simplenotes.junit-convention") - id("org.jlleitschuh.gradle.ktlint") + id("com.diffplug.spotless") +} + +spotless { + kotlin { + ktlint("0.48.0").setEditorConfigPath(project.rootProject.file(".editorconfig")) + } } diff --git a/buildSrc/src/main/kotlin/be/simplenotes/versions.gradle.kts b/buildSrc/src/main/kotlin/be/simplenotes/versions.gradle.kts index 5ec9bb2..b04ed73 100644 --- a/buildSrc/src/main/kotlin/be/simplenotes/versions.gradle.kts +++ b/buildSrc/src/main/kotlin/be/simplenotes/versions.gradle.kts @@ -10,11 +10,7 @@ tasks.named("dependencyUpdates").configure { resolutionStrategy { componentSelection { all { - if ("RC" in candidate.version) reject("Release candidate") - - when { - candidate.group == "org.eclipse.jetty" && candidate.version.startsWith("11.") -> reject("javax -> jakarta") - } + if ("alpha|beta|rc".toRegex().containsMatchIn(candidate.version.lowercase())) reject("Non stable version") } } } diff --git a/buildSrc/src/main/resources/exclusions/excluded-resources.txt b/buildSrc/src/main/resources/exclusions/excluded-resources.txt index df200aa..d2f91c7 100644 --- a/buildSrc/src/main/resources/exclusions/excluded-resources.txt +++ b/buildSrc/src/main/resources/exclusions/excluded-resources.txt @@ -1,5 +1,6 @@ META-INF/maven/** META-INF/proguard/** +META-INF/com.android.tools/** META-INF/*.kotlin_module META-INF/DEPENDENCIES* META-INF/NOTICE* @@ -7,6 +8,7 @@ META-INF/LICENSE* LICENSE* META-INF/README* META-INF/native-image/** +**/module-info.** # Jetty about.html diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 5194818..c89e1a0 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -7,6 +7,7 @@ plugins { dependencies { micronaut() + runtimeOnly(libs.yaml) testImplementation(libs.bundles.test) testRuntimeOnly(libs.slf4j.logback) diff --git a/config/src/DataConfig.kt b/config/src/DataConfig.kt index dcc4732..dae7819 100644 --- a/config/src/DataConfig.kt +++ b/config/src/DataConfig.kt @@ -3,7 +3,7 @@ package be.simplenotes.config import io.micronaut.context.annotation.* import java.nio.file.Path import java.util.concurrent.TimeUnit -import javax.inject.Singleton +import jakarta.inject.Singleton data class DataConfig(val dataDir: String) @@ -36,7 +36,7 @@ class ConfigFactory { fun datasourceConfig(dataConfig: DataConfig) = DataSourceConfig( jdbcUrl = "jdbc:h2:" + Path.of(dataConfig.dataDir, "simplenotes").normalize().toAbsolutePath(), maximumPoolSize = 10, - connectionTimeout = 1000 + connectionTimeout = 1000, ) @Singleton @@ -44,7 +44,7 @@ class ConfigFactory { fun testDatasourceConfig() = DataSourceConfig( jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1", maximumPoolSize = 2, - connectionTimeout = 1000 + connectionTimeout = 1000, ) @Singleton @@ -54,5 +54,4 @@ class ConfigFactory { @Singleton @Requires(env = ["test"]) fun testDataConfig() = DataConfig("/tmp") - } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index fc70ed6..daaf465 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { implementation(project(":persistence")) implementation(project(":search")) - api(libs.arrow.core.data) + api(libs.arrow.core) api(libs.konform) micronaut() diff --git a/domain/src/ExportService.kt b/domain/src/ExportService.kt index a3e1fca..bc113ca 100644 --- a/domain/src/ExportService.kt +++ b/domain/src/ExportService.kt @@ -9,7 +9,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream -import javax.inject.Singleton +import jakarta.inject.Singleton interface ExportService { fun exportAsJson(userId: Int): String diff --git a/domain/src/MarkdownService.kt b/domain/src/MarkdownService.kt index b1688d8..9aa5d61 100644 --- a/domain/src/MarkdownService.kt +++ b/domain/src/MarkdownService.kt @@ -1,50 +1,47 @@ package be.simplenotes.domain import arrow.core.Either -import arrow.core.computations.either -import arrow.core.left -import arrow.core.right +import arrow.core.raise.either +import arrow.core.raise.ensure import be.simplenotes.domain.validation.NoteValidations import be.simplenotes.types.NoteMetadata import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.parser.Parser import io.konform.validation.ValidationErrors +import jakarta.inject.Singleton import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.parser.ParserException import org.yaml.snakeyaml.scanner.ScannerException -import javax.inject.Singleton interface MarkdownService { fun renderDocument(input: String): Either } -private typealias MetaMdPair = Pair - @Singleton internal class MarkdownServiceImpl( private val parser: Parser, private val renderer: HtmlRenderer, ) : MarkdownService { private val yamlBoundPattern = "-{3}".toRegex() - private fun splitMetaFromDocument(input: String): Either { + private fun splitMetaFromDocument(input: String) = either { val split = input.split(yamlBoundPattern, 3) - if (split.size < 3) return MarkdownParsingError.MissingMeta.left() - return (split[1].trim() to split[2].trim()).right() + ensure(split.size >= 3) { MarkdownParsingError.MissingMeta } + (split[1].trim() to split[2].trim()) } private val yaml = Yaml() - private fun parseMeta(input: String): Either { + private fun parseMeta(input: String) = either { val load: Map = try { yaml.load(input) } catch (e: ParserException) { - return MarkdownParsingError.InvalidMeta.left() + raise(MarkdownParsingError.InvalidMeta) } catch (e: ScannerException) { - return MarkdownParsingError.InvalidMeta.left() + raise(MarkdownParsingError.InvalidMeta) } val title = when (val titleNode = load["title"]) { is String, is Number -> titleNode.toString() - else -> return MarkdownParsingError.InvalidMeta.left() + else -> raise(MarkdownParsingError.InvalidMeta) } val tagsNode = load["tags"] @@ -53,15 +50,15 @@ internal class MarkdownServiceImpl( else tagsNode.map { it.toString() } - return NoteMetadata(title, tags).right() + NoteMetadata(title, tags) } private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render) - override fun renderDocument(input: String) = either.eager { - val (meta, md) = !splitMetaFromDocument(input) - val parsedMeta = !parseMeta(meta) - !Either.fromNullable(NoteValidations.validateMetadata(parsedMeta)).swap() + override fun renderDocument(input: String) = either { + val (meta, md) = splitMetaFromDocument(input).bind() + val parsedMeta = parseMeta(meta).bind() + NoteValidations.validateMetadata(parsedMeta)?.let { raise(it) } val html = renderMarkdown(md) Document(parsedMeta, html) } diff --git a/domain/src/NoteService.kt b/domain/src/NoteService.kt index a406874..e22f952 100644 --- a/domain/src/NoteService.kt +++ b/domain/src/NoteService.kt @@ -1,6 +1,6 @@ package be.simplenotes.domain -import arrow.core.computations.either +import arrow.core.raise.either import be.simplenotes.domain.security.HtmlSanitizer import be.simplenotes.domain.utils.parseSearchTerms import be.simplenotes.persistence.repositories.NoteRepository @@ -8,12 +8,11 @@ import be.simplenotes.persistence.repositories.UserRepository import be.simplenotes.search.NoteSearcher import be.simplenotes.types.LoggedInUser import be.simplenotes.types.Note -import be.simplenotes.types.PersistedNote import be.simplenotes.types.PersistedNoteMetadata +import jakarta.annotation.PostConstruct +import jakarta.annotation.PreDestroy +import jakarta.inject.Singleton import java.util.* -import javax.annotation.PostConstruct -import javax.annotation.PreDestroy -import javax.inject.Singleton @Singleton class NoteService( @@ -21,10 +20,10 @@ class NoteService( private val noteRepository: NoteRepository, private val userRepository: UserRepository, private val searcher: NoteSearcher, - private val htmlSanitizer: HtmlSanitizer + private val htmlSanitizer: HtmlSanitizer, ) { - fun create(user: LoggedInUser, markdownText: String) = either.eager { + fun create(user: LoggedInUser, markdownText: String) = either { markdownService.renderDocument(markdownText) .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } .map { Note(title = it.metadata.title, tags = it.metadata.tags, markdown = markdownText, html = it.html) } @@ -34,7 +33,7 @@ class NoteService( } fun update(user: LoggedInUser, uuid: UUID, markdownText: String) = - either.eager { + either { markdownService.renderDocument(markdownText) .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } .map { @@ -42,7 +41,7 @@ class NoteService( title = it.metadata.title, tags = it.metadata.tags, markdown = markdownText, - html = it.html + html = it.html, ) } .map { noteRepository.update(user.userId, uuid, it) } diff --git a/domain/src/UserService.kt b/domain/src/UserService.kt index 65cf163..001153f 100644 --- a/domain/src/UserService.kt +++ b/domain/src/UserService.kt @@ -1,10 +1,9 @@ package be.simplenotes.domain import arrow.core.Either -import arrow.core.computations.either -import arrow.core.filterOrElse -import arrow.core.leftIfNull -import arrow.core.rightIfNotNull +import arrow.core.raise.either +import arrow.core.raise.ensure +import arrow.core.raise.ensureNotNull import be.simplenotes.domain.security.PasswordHash import be.simplenotes.domain.security.SimpleJwt import be.simplenotes.domain.validation.UserValidations @@ -13,8 +12,8 @@ import be.simplenotes.search.NoteSearcher import be.simplenotes.types.LoggedInUser import be.simplenotes.types.PersistedUser import io.konform.validation.ValidationErrors +import jakarta.inject.Singleton import kotlinx.serialization.Serializable -import javax.inject.Singleton interface UserService { fun register(form: RegisterForm): Either @@ -30,31 +29,26 @@ internal class UserServiceImpl( private val searcher: NoteSearcher, ) : UserService { - override fun register(form: RegisterForm) = UserValidations.validateRegister(form) - .filterOrElse({ !userRepository.exists(it.username) }, { RegisterError.UserExists }) - .map { it.copy(password = passwordHash.crypt(it.password)) } - .map { userRepository.create(it) } - .leftIfNull { RegisterError.UserExists } - - override fun login(form: LoginForm) = either.eager { - UserValidations.validateLogin(form) - .bind() - .let { userRepository.find(it.username) } - .rightIfNotNull { LoginError.Unregistered } - .filterOrElse({ passwordHash.verify(form.password!!, it.password) }, { LoginError.WrongPassword }) - .map { jwt.sign(LoggedInUser(it)) } - .bind() + override fun register(form: RegisterForm) = either { + val user = UserValidations.validateRegister(form).bind() + ensure(!userRepository.exists(user.username)) { RegisterError.UserExists } + ensureNotNull(userRepository.create(user.copy(password = passwordHash.crypt(user.password)))) { + RegisterError.UserExists + } } - override fun delete(form: DeleteForm) = either.eager { - val user = !UserValidations.validateDelete(form) - val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered } - !Either.conditionally( - passwordHash.verify(user.password, persistedUser.password), - { DeleteError.WrongPassword }, - { } - ) - !Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { }) + override fun login(form: LoginForm) = either { + val user = UserValidations.validateLogin(form).bind() + val persistedUser = ensureNotNull(userRepository.find(user.username)) { LoginError.Unregistered } + ensure(passwordHash.verify(form.password!!, persistedUser.password)) { LoginError.WrongPassword } + jwt.sign(LoggedInUser(persistedUser)) + } + + override fun delete(form: DeleteForm) = either { + val user = UserValidations.validateDelete(form).bind() + val persistedUser = ensureNotNull(userRepository.find(user.username)) { DeleteError.Unregistered } + ensure(passwordHash.verify(user.password, persistedUser.password)) { DeleteError.WrongPassword } + ensure(userRepository.delete(persistedUser.id)) { DeleteError.Unregistered } searcher.dropIndex(persistedUser.id) } } diff --git a/domain/src/modules/FlexmarkFactory.kt b/domain/src/modules/FlexmarkFactory.kt index 560d401..471b198 100644 --- a/domain/src/modules/FlexmarkFactory.kt +++ b/domain/src/modules/FlexmarkFactory.kt @@ -5,7 +5,7 @@ import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.parser.Parser import com.vladsch.flexmark.util.data.MutableDataSet import io.micronaut.context.annotation.Factory -import javax.inject.Singleton +import jakarta.inject.Singleton @Factory class FlexmarkFactory { @@ -23,11 +23,11 @@ class FlexmarkFactory { set(TaskListExtension.LOOSE_ITEM_CLASS, "") set( TaskListExtension.ITEM_DONE_MARKER, - """ """ + """ """, ) set( TaskListExtension.ITEM_NOT_DONE_MARKER, - """ """ + """ """, ) set(HtmlRenderer.SOFT_BREAK, "
") } diff --git a/domain/src/security/HtmlSanitizer.kt b/domain/src/security/HtmlSanitizer.kt index 7c39244..a79dae9 100644 --- a/domain/src/security/HtmlSanitizer.kt +++ b/domain/src/security/HtmlSanitizer.kt @@ -4,7 +4,7 @@ import be.simplenotes.types.LoggedInUser import org.owasp.html.HtmlChangeListener import org.owasp.html.HtmlPolicyBuilder import org.slf4j.LoggerFactory -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class HtmlSanitizer { diff --git a/domain/src/security/JwtMapper.kt b/domain/src/security/JwtMapper.kt index 3a992e0..fd72c83 100644 --- a/domain/src/security/JwtMapper.kt +++ b/domain/src/security/JwtMapper.kt @@ -3,7 +3,7 @@ package be.simplenotes.domain.security import be.simplenotes.types.LoggedInUser import com.auth0.jwt.JWTCreator import com.auth0.jwt.interfaces.DecodedJWT -import javax.inject.Singleton +import jakarta.inject.Singleton interface JwtMapper { fun extract(decodedJWT: DecodedJWT): T? diff --git a/domain/src/security/PasswordHash.kt b/domain/src/security/PasswordHash.kt index 003a59c..c8aa980 100644 --- a/domain/src/security/PasswordHash.kt +++ b/domain/src/security/PasswordHash.kt @@ -1,8 +1,8 @@ package be.simplenotes.domain.security import org.mindrot.jbcrypt.BCrypt -import javax.inject.Inject -import javax.inject.Singleton +import jakarta.inject.Inject +import jakarta.inject.Singleton internal interface PasswordHash { fun crypt(password: String): String diff --git a/domain/src/security/SimpleJwt.kt b/domain/src/security/SimpleJwt.kt index b10810e..b492380 100644 --- a/domain/src/security/SimpleJwt.kt +++ b/domain/src/security/SimpleJwt.kt @@ -7,7 +7,7 @@ import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.exceptions.JWTVerificationException import java.util.* import java.util.concurrent.TimeUnit -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton class SimpleJwt(jwtConfig: JwtConfig, private val mapper: JwtMapper) { diff --git a/domain/src/utils/SearchTermsParser.kt b/domain/src/utils/SearchTermsParser.kt index b4d4f8d..ec945f2 100644 --- a/domain/src/utils/SearchTermsParser.kt +++ b/domain/src/utils/SearchTermsParser.kt @@ -89,6 +89,6 @@ internal fun parseSearchTerms(input: String): SearchTerms { title = title, tag = tag, content = content, - all = all + all = all, ) } diff --git a/domain/src/validation/UserValidations.kt b/domain/src/validation/UserValidations.kt index 9b113d3..1e5a706 100644 --- a/domain/src/validation/UserValidations.kt +++ b/domain/src/validation/UserValidations.kt @@ -1,8 +1,7 @@ package be.simplenotes.domain.validation -import arrow.core.Either -import arrow.core.left -import arrow.core.right +import arrow.core.raise.either +import arrow.core.raise.ensure import be.simplenotes.domain.* import be.simplenotes.types.User import io.konform.validation.Validation @@ -21,16 +20,16 @@ internal object UserValidations { } } - fun validateLogin(form: LoginForm): Either { + fun validateLogin(form: LoginForm) = either { val errors = loginValidator.validate(form).errors - return if (errors.isEmpty()) User(form.username!!, form.password!!).right() - else return LoginError.InvalidLoginForm(errors).left() + ensure(errors.isEmpty()) { LoginError.InvalidLoginForm(errors) } + User(form.username!!, form.password!!) } - fun validateRegister(form: RegisterForm): Either { + fun validateRegister(form: RegisterForm) = either { val errors = loginValidator.validate(form).errors - return if (errors.isEmpty()) User(form.username!!, form.password!!).right() - else return RegisterError.InvalidRegisterForm(errors).left() + ensure(errors.isEmpty()) { RegisterError.InvalidRegisterForm(errors) } + User(form.username!!, form.password!!) } private val deleteValidator = Validation { @@ -47,9 +46,9 @@ internal object UserValidations { } } - fun validateDelete(form: DeleteForm): Either { + fun validateDelete(form: DeleteForm) = either { val errors = deleteValidator.validate(form).errors - return if (errors.isEmpty()) User(form.username!!, form.password!!).right() - else return DeleteError.InvalidForm(errors).left() + ensure(errors.isEmpty()) { DeleteError.InvalidForm(errors) } + User(form.username!!, form.password!!) } } diff --git a/domain/test/security/LoggedInUserExtractorTest.kt b/domain/test/security/LoggedInUserExtractorTest.kt index d85ed4d..4a25645 100644 --- a/domain/test/security/LoggedInUserExtractorTest.kt +++ b/domain/test/security/LoggedInUserExtractorTest.kt @@ -33,7 +33,7 @@ internal class LoggedInUserExtractorTest { createToken(), createToken(username = "user", id = 1, secret = "not the correct secret"), createToken(username = "user", id = 1) + "\"efesfsef", - "something that is not even a token" + "something that is not even a token", ) @ParameterizedTest(name = "[{index}] token `{0}` should be invalid") diff --git a/domain/test/testutils/ArrowAssertions.kt b/domain/test/testutils/ArrowAssertions.kt index e66e53b..d66a2fb 100644 --- a/domain/test/testutils/ArrowAssertions.kt +++ b/domain/test/testutils/ArrowAssertions.kt @@ -21,7 +21,7 @@ fun isRight() = object : Matcher> { override fun invoke(actual: Either<*, *>) = when (actual) { is Either.Right -> MatchResult.Match is Either.Left -> { - val valueA = actual.a + val valueA = actual.value MatchResult.Mismatch("is Either.Left<${if (valueA == null) "Null" else valueA::class.simpleName}>") } } @@ -34,7 +34,7 @@ inline fun isLeftOfType() = object : Matcher> { override fun invoke(actual: Either<*, *>) = when (actual) { is Either.Right -> MatchResult.Mismatch("was Either.Right<>") is Either.Left -> { - val valueA = actual.a + val valueA = actual.value if (valueA is A) MatchResult.Match else MatchResult.Mismatch("was Left<${if (valueA == null) "Null" else valueA::class.simpleName}>") } diff --git a/domain/test/utils/SearchTermsParserKtTest.kt b/domain/test/utils/SearchTermsParserKtTest.kt index 273c17a..daf2f50 100644 --- a/domain/test/utils/SearchTermsParserKtTest.kt +++ b/domain/test/utils/SearchTermsParserKtTest.kt @@ -33,7 +33,7 @@ internal class SearchTermsParserKtTest { "tag:'example abc' title:'other with words' this is the end ", title = "other with words", tag = "example abc", - all = "this is the end" + all = "this is the end", ), createResult("tag:blah", tag = "blah"), createResult("tag:'some words'", tag = "some words"), @@ -41,7 +41,7 @@ internal class SearchTermsParserKtTest { createResult( "tag:'double quote inside single \" ' global", tag = "double quote inside single \" ", - all = "global" + all = "global", ), ) diff --git a/domain/test/validation/UserValidationsTest.kt b/domain/test/validation/UserValidationsTest.kt index 8924ab0..94b0240 100644 --- a/domain/test/validation/UserValidationsTest.kt +++ b/domain/test/validation/UserValidationsTest.kt @@ -22,7 +22,7 @@ internal class UserValidationsTest { LoginForm(username = "", password = ""), LoginForm(username = "a", password = "aaaa"), LoginForm(username = "a".repeat(51), password = "a".repeat(8)), - LoginForm(username = "a".repeat(10), password = "a".repeat(7)) + LoginForm(username = "a".repeat(10), password = "a".repeat(7)), ) @ParameterizedTest @@ -34,7 +34,7 @@ internal class UserValidationsTest { @Suppress("Unused") fun validLoginForms(): Stream = Stream.of( LoginForm(username = "a".repeat(50), password = "a".repeat(72)), - LoginForm(username = "a".repeat(3), password = "a".repeat(8)) + LoginForm(username = "a".repeat(3), password = "a".repeat(8)), ) @ParameterizedTest @@ -53,7 +53,7 @@ internal class UserValidationsTest { RegisterForm(username = "", password = ""), RegisterForm(username = "a", password = "aaaa"), RegisterForm(username = "a".repeat(51), password = "a".repeat(8)), - RegisterForm(username = "a".repeat(10), password = "a".repeat(7)) + RegisterForm(username = "a".repeat(10), password = "a".repeat(7)), ) @ParameterizedTest @@ -65,7 +65,7 @@ internal class UserValidationsTest { @Suppress("Unused") fun validRegisterForms(): Stream = Stream.of( RegisterForm(username = "a".repeat(50), password = "a".repeat(72)), - RegisterForm(username = "a".repeat(3), password = "a".repeat(8)) + RegisterForm(username = "a".repeat(3), password = "a".repeat(8)), ) @ParameterizedTest diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8bcb7f..aa12de4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,22 @@ [versions] -flexmark = "0.62.2" -lucene = "8.8.2" -http4k = "4.8.0.0" -jetty = "10.0.2" -mapstruct = "1.4.2.Final" -micronaut-inject = "2.5.1" +flexmark = "0.64.4" +lucene = "9.5.0" +http4k = "4.43.0.0" +jetty = "11.0.15" +mapstruct = "1.5.5.Final" +micronaut-inject = "4.0.0-M2" [libraries] flexmark-core = { module = "com.vladsch.flexmark:flexmark", version.ref = "flexmark" } flexmark-tasklist = { module = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist", version.ref = "flexmark" } -flyway = { module = "org.flywaydb:flyway-core", version = "7.8.2" } -hikariCP = { module = "com.zaxxer:HikariCP", version = "4.0.3" } -h2 = { module = "com.h2database:h2", version = "1.4.200" } -ktorm = { module = "org.ktorm:ktorm-core", version = "3.3.0" } +flyway = { module = "org.flywaydb:flyway-core", version = "9.17.0" } +hikariCP = { module = "com.zaxxer:HikariCP", version = "5.0.1" } +h2 = { module = "com.h2database:h2", version = "2.1.214" } +ktorm = { module = "org.ktorm:ktorm-core", version = "3.6.0" } lucene-core = { module = "org.apache.lucene:lucene-core", version.ref = "lucene" } -lucene-analyzers-common = { module = "org.apache.lucene:lucene-analyzers-common", version.ref = "lucene" } +lucene-analyzers-common = { module = "org.apache.lucene:lucene-analysis-common", version.ref = "lucene" } lucene-queryparser = { module = "org.apache.lucene:lucene-queryparser", version.ref = "lucene" } http4k-core = { module = "org.http4k:http4k-core", version.ref = "http4k" } @@ -24,13 +24,13 @@ http4k-testing-hamkrest = { module = "org.http4k:http4k-testing-hamkrest", versi jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "jetty" } jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "jetty" } -javax-servlet = { module = "javax.servlet:javax.servlet-api", version = "4.0.1" } +javax-servlet = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.0.0" } -kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version = "0.7.3" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version = "1.2.0" } +kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version = "0.8.1" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version = "1.5.0" } -slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.0-alpha1" } -slf4j-logback = { module = "ch.qos.logback:logback-classic", version = "1.3.0-alpha5" } +slf4j-api = { module = "org.slf4j:slf4j-api", version = "2.0.7" } +slf4j-logback = { module = "ch.qos.logback:logback-classic", version = "1.4.7" } mapstruct-core = { module = "org.mapstruct:mapstruct", version.ref = "mapstruct" } mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.ref = "mapstruct" } @@ -38,19 +38,19 @@ mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.re micronaut-inject = { module = "io.micronaut:micronaut-inject", version.ref = "micronaut-inject" } micronaut-processor = { module = "io.micronaut:micronaut-inject-java", version.ref = "micronaut-inject" } -arrow-core-data = { module = "io.arrow-kt:arrow-core-data", version = "0.11.0" } -commonsCompress = { module = "org.apache.commons:commons-compress", version = "1.20" } -jwt = { module = "com.auth0:java-jwt", version = "3.15.0" } +arrow-core = { module = "io.arrow-kt:arrow-core", version = "1.2.0-RC" } +commonsCompress = { module = "org.apache.commons:commons-compress", version = "1.23.0" } +jwt = { module = "com.auth0:java-jwt", version = "4.4.0" } bcrypt = { module = "org.mindrot:jbcrypt", version = "0.4" } -konform = { module = "io.konform:konform-jvm", version = "0.2.0" } -owasp-html-sanitizer = { module = "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer", version = "20200713.1" } -prettytime = { module = "org.ocpsoft.prettytime:prettytime", version = "5.0.1.Final" } -yaml = { module = "org.yaml:snakeyaml", version = "1.28" } +konform = { module = "io.konform:konform-jvm", version = "0.4.0" } +owasp-html-sanitizer = { module = "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer", version = "20220608.1" } +prettytime = { module = "org.ocpsoft.prettytime:prettytime", version = "5.0.6.Final" } +yaml = { module = "org.yaml:snakeyaml", version = "2.0" } -assertJ = { module = "org.assertj:assertj-core", version = "3.19.0" } +assertJ = { module = "org.assertj:assertj-core", version = "3.24.2" } hamkrest = { module = "com.natpryce:hamkrest", version = "1.8.0.1" } -junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version = "5.7.1" } -mockk = { module = "io.mockk:mockk", version = "1.11.0" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version = "5.9.3" } +mockk = { module = "io.mockk:mockk", version = "1.13.5" } faker = { module = "com.github.javafaker:javafaker", version = "1.0.2" } [bundles] diff --git a/persistence/src/Migrations.kt b/persistence/src/Migrations.kt index f8192a3..0de7194 100644 --- a/persistence/src/Migrations.kt +++ b/persistence/src/Migrations.kt @@ -1,7 +1,7 @@ package be.simplenotes.persistence import org.flywaydb.core.Flyway -import javax.inject.Singleton +import jakarta.inject.Singleton import javax.sql.DataSource interface DbMigrations { @@ -14,6 +14,7 @@ internal class DbMigrationsImpl(private val dataSource: DataSource) : DbMigratio Flyway.configure() .dataSource(dataSource) .locations("db/migration") + .loggers("slf4j") .load() .migrate() } diff --git a/persistence/src/PersistenceModule.kt b/persistence/src/PersistenceModule.kt index ed76eb3..410c4eb 100644 --- a/persistence/src/PersistenceModule.kt +++ b/persistence/src/PersistenceModule.kt @@ -8,7 +8,7 @@ import io.micronaut.context.annotation.Bean import io.micronaut.context.annotation.Factory import org.ktorm.database.Database import org.ktorm.database.SqlDialect -import javax.inject.Singleton +import jakarta.inject.Singleton import javax.sql.DataSource @Factory @@ -17,10 +17,13 @@ class PersistenceModule { @Singleton internal fun database(migrations: DbMigrations, dataSource: DataSource): Database { migrations.migrate() - return Database.connect(dataSource, dialect = object : SqlDialect { - override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int) = - CustomSqlFormatter(database, beautifySql, indentSize) - }) + return Database.connect( + dataSource, + dialect = object : SqlDialect { + override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int) = + CustomSqlFormatter(database, beautifySql, indentSize) + }, + ) } @Singleton diff --git a/persistence/src/extensions/KtormExtensions.kt b/persistence/src/extensions/KtormExtensions.kt index 7a07c63..aa0e927 100644 --- a/persistence/src/extensions/KtormExtensions.kt +++ b/persistence/src/extensions/KtormExtensions.kt @@ -11,8 +11,9 @@ internal class VarcharArraySqlType : SqlType>(Types.ARRAY, typeName override fun doGetResult(rs: ResultSet, index: Int): List? { return when (val array = rs.getObject(index)) { null -> null + is java.sql.Array -> (array.array as Array<*>).filterNotNull().map { it.toString() } is Array<*> -> array.map { it.toString() } - else -> error("") + else -> error("Unable to deserialize varchar[]") } } @@ -27,7 +28,7 @@ data class ArrayContainsExpression( val left: ScalarExpression<*>, val right: ScalarExpression<*>, override val sqlType: SqlType = BooleanSqlType, - override val isLeafNode: Boolean = false + override val isLeafNode: Boolean = false, ) : ScalarExpression() { override val extraProperties: Map get() = emptyMap() } diff --git a/persistence/src/repositories/NoteRepository.kt b/persistence/src/repositories/NoteRepository.kt index 40e9e88..dc91bc0 100644 --- a/persistence/src/repositories/NoteRepository.kt +++ b/persistence/src/repositories/NoteRepository.kt @@ -13,7 +13,7 @@ interface NoteRepository { limit: Int = 20, offset: Int = 0, tag: String? = null, - deleted: Boolean = false + deleted: Boolean = false, ): List fun count(userId: Int, tag: String? = null, deleted: Boolean = false): Int diff --git a/persistence/src/repositories/NoteRepositoryImpl.kt b/persistence/src/repositories/NoteRepositoryImpl.kt index 992c41a..585765c 100644 --- a/persistence/src/repositories/NoteRepositoryImpl.kt +++ b/persistence/src/repositories/NoteRepositoryImpl.kt @@ -10,7 +10,7 @@ import org.ktorm.dsl.* import org.ktorm.entity.* import java.time.LocalDateTime import java.util.* -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton internal class NoteRepositoryImpl( @@ -40,14 +40,18 @@ internal class NoteRepositoryImpl( val uuid = UUID.randomUUID() val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now()) db.notes.add(entity) - db.batchInsert(Tags) { - note.tags.forEach { tagName -> - item { - set(it.noteUuid, uuid) - set(it.name, tagName) + + note.tags.takeIf { it.isNotEmpty() }?.run { + db.batchInsert(Tags) { + forEach { tagName -> + item { + set(it.noteUuid, uuid) + set(it.name, tagName) + } } } } + return find(userId, uuid) ?: error("Note not found") } diff --git a/persistence/src/repositories/UserRepositoryImpl.kt b/persistence/src/repositories/UserRepositoryImpl.kt index 7ff9d89..5bbff76 100644 --- a/persistence/src/repositories/UserRepositoryImpl.kt +++ b/persistence/src/repositories/UserRepositoryImpl.kt @@ -10,7 +10,7 @@ import org.ktorm.dsl.* import org.ktorm.entity.any import org.ktorm.entity.find import java.sql.SQLIntegrityConstraintViolationException -import javax.inject.Singleton +import jakarta.inject.Singleton @Singleton internal class UserRepositoryImpl( diff --git a/persistence/test/DbTest.kt b/persistence/test/DbTest.kt index 838216a..c37c210 100644 --- a/persistence/test/DbTest.kt +++ b/persistence/test/DbTest.kt @@ -11,7 +11,7 @@ import javax.sql.DataSource @ResourceLock("h2") abstract class DbTest { - val beanContext = ApplicationContext.build().deduceEnvironment(false).environments("test").start() + val beanContext = ApplicationContext.builder().deduceEnvironment(false).environments("test").start() inline fun BeanContext.getBean(): T = getBean(T::class.java) @@ -22,6 +22,8 @@ abstract class DbTest { Flyway.configure() .dataSource(dataSource) + .loggers("slf4j") + .cleanDisabled(false) .load() .clean() diff --git a/persistence/test/NoteRepositoryImplTest.kt b/persistence/test/NoteRepositoryImplTest.kt index b4eaf04..5d71a99 100644 --- a/persistence/test/NoteRepositoryImplTest.kt +++ b/persistence/test/NoteRepositoryImplTest.kt @@ -13,7 +13,6 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import org.junit.jupiter.api.parallel.ResourceLock import org.ktorm.database.Database import org.ktorm.dsl.eq import org.ktorm.entity.filter @@ -22,7 +21,6 @@ import org.ktorm.entity.mapColumns import org.ktorm.entity.toList import java.sql.SQLIntegrityConstraintViolationException - internal class NoteRepositoryImplTest : DbTest() { private lateinit var noteRepo: NoteRepository @@ -86,14 +84,14 @@ internal class NoteRepositoryImplTest : DbTest() { .hasSize(3) .usingElementComparatorIgnoringFields("updatedAt") .containsExactlyInAnyOrderElementsOf( - notes1.map { it.toPersistedMeta() } + notes1.map { it.toPersistedMeta() }, ) assertThat(noteRepo.findAll(user2.id)) .hasSize(1) .usingElementComparatorIgnoringFields("updatedAt") .containsExactlyInAnyOrderElementsOf( - notes2.map { it.toPersistedMeta() } + notes2.map { it.toPersistedMeta() }, ) assertThat(noteRepo.findAll(1000)).isEmpty() @@ -131,7 +129,9 @@ internal class NoteRepositoryImplTest : DbTest() { val note = db.notes.find { Notes.title eq fakeNote.title }!! .let { entity -> - val tags = db.tags.filter { be.simplenotes.persistence.Tags.noteUuid eq entity.uuid }.mapColumns { be.simplenotes.persistence.Tags.name } as List + val tags = db.tags.filter { + be.simplenotes.persistence.Tags.noteUuid eq entity.uuid + }.mapColumns { be.simplenotes.persistence.Tags.name } as List PersistedNote( uuid = entity.uuid, title = entity.title, diff --git a/search/src/Extractors.kt b/search/src/Extractors.kt index 4b43553..027d151 100644 --- a/search/src/Extractors.kt +++ b/search/src/Extractors.kt @@ -22,5 +22,5 @@ internal fun Document.toNoteMeta() = PersistedNoteMetadata( title = get(titleField), uuid = UuidFieldConverter.fromDoc(get(uuidField)), updatedAt = LocalDateTimeFieldConverter.fromDoc(get(updatedAtField)), - tags = TagsFieldConverter.fromDoc(get(tagsField)) + tags = TagsFieldConverter.fromDoc(get(tagsField)), ) diff --git a/search/src/NoteSearcherImpl.kt b/search/src/NoteSearcherImpl.kt index ca6e787..a7039f0 100644 --- a/search/src/NoteSearcherImpl.kt +++ b/search/src/NoteSearcherImpl.kt @@ -12,8 +12,8 @@ import org.slf4j.LoggerFactory import java.io.File import java.nio.file.Path import java.util.* -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton internal class NoteSearcherImpl(@Named("search-index") basePath: Path) : NoteSearcher { diff --git a/search/src/SearchModule.kt b/search/src/SearchModule.kt index fd5f7bc..93fb3f3 100644 --- a/search/src/SearchModule.kt +++ b/search/src/SearchModule.kt @@ -4,7 +4,7 @@ import be.simplenotes.config.DataConfig import io.micronaut.context.annotation.Factory import io.micronaut.context.annotation.Prototype import java.nio.file.Path -import javax.inject.Named +import jakarta.inject.Named @Factory class SearchModule { diff --git a/search/test/NoteSearcherImplTest.kt b/search/test/NoteSearcherImplTest.kt index dc4eeca..971f327 100644 --- a/search/test/NoteSearcherImplTest.kt +++ b/search/test/NoteSearcherImplTest.kt @@ -31,7 +31,7 @@ internal class NoteSearcherImplTest { html = "", updatedAt = LocalDateTime.MIN, uuid = uuid, - public = false + public = false, ) searcher.indexNote(1, note) return note diff --git a/views/src/BaseView.kt b/views/src/BaseView.kt index a1986a2..6d0a8eb 100644 --- a/views/src/BaseView.kt +++ b/views/src/BaseView.kt @@ -3,15 +3,15 @@ package be.simplenotes.views import be.simplenotes.types.LoggedInUser import kotlinx.html.* import kotlinx.html.ThScope.col -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class BaseView(@Named("styles") styles: String) : View(styles) { fun renderHome(loggedInUser: LoggedInUser?) = renderPage( title = "Home", description = "A fast and simple note taking website", - loggedInUser = loggedInUser + loggedInUser = loggedInUser, ) { section("text-center my-2 p-2") { h1("text-5xl casual") { @@ -21,7 +21,6 @@ class BaseView(@Named("styles") styles: String) : View(styles) { } 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") { attributes["aria-label"] = "demo" div("flex justify-between mb-4") { diff --git a/views/src/ErrorView.kt b/views/src/ErrorView.kt index aaf8802..8ebfa70 100644 --- a/views/src/ErrorView.kt +++ b/views/src/ErrorView.kt @@ -4,8 +4,8 @@ import be.simplenotes.views.components.Alert import be.simplenotes.views.components.alert import kotlinx.html.a import kotlinx.html.div -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class ErrorView(@Named("styles") styles: String) : View(styles) { @@ -23,7 +23,7 @@ class ErrorView(@Named("styles") styles: String) : View(styles) { Alert.Warning, errorType.title, "Please try again later", - multiline = true + multiline = true, ) Type.NotFound -> alert(Alert.Warning, errorType.title, "Page not found", multiline = true) Type.Other -> alert(Alert.Warning, errorType.title) diff --git a/views/src/NoteView.kt b/views/src/NoteView.kt index 54b5d48..c560e01 100644 --- a/views/src/NoteView.kt +++ b/views/src/NoteView.kt @@ -6,8 +6,8 @@ import be.simplenotes.types.PersistedNoteMetadata import be.simplenotes.views.components.* import io.konform.validation.ValidationError import kotlinx.html.* -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class NoteView(@Named("styles") styles: String) : View(styles) { @@ -41,7 +41,7 @@ class NoteView(@Named("styles") styles: String) : View(styles) { |--- | """.trimMargin( - "|" + "|", ) } submitButton("Save") @@ -123,10 +123,9 @@ class NoteView(@Named("styles") styles: String) : View(styles) { fun renderedNote(loggedInUser: LoggedInUser?, note: PersistedNote, shared: Boolean) = renderPage( note.title, loggedInUser = loggedInUser, - scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js") + scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js"), ) { div("container mx-auto p-4") { - if (shared) { p("p-4 bg-gray-800") { +"You are viewing a public note " @@ -172,14 +171,14 @@ class NoteView(@Named("styles") styles: String) : View(styles) { form(method = FormMethod.post, classes = "inline flex space-x-2 justify-end mb-4") { a( href = "/notes/${note.uuid}/edit", - classes = "btn btn-green" + classes = "btn btn-green", ) { +"Edit" } span { button( type = ButtonType.submit, name = if (note.public) "private" else "public", classes = "font-semibold border-b-4 ${if (note.public) "border-teal-200" else "border-green-500"}" + - " p-2 rounded-l bg-teal-200 text-gray-800" + " p-2 rounded-l bg-teal-200 text-gray-800", ) { +"Private" } @@ -188,7 +187,7 @@ class NoteView(@Named("styles") styles: String) : View(styles) { name = if (note.public) "private" else "public", classes = "font-semibold border-b-4 " + (if (!note.public) "border-teal-200" else "border-green-500") + - " p-2 rounded-r bg-teal-200 text-gray-800" + " p-2 rounded-r bg-teal-200 text-gray-800", ) { +"Public" } @@ -196,7 +195,7 @@ class NoteView(@Named("styles") styles: String) : View(styles) { button( type = ButtonType.submit, name = "delete", - classes = "btn btn-red" + classes = "btn btn-red", ) { +"Delete" } } } diff --git a/views/src/SettingView.kt b/views/src/SettingView.kt index 81876cb..effb031 100644 --- a/views/src/SettingView.kt +++ b/views/src/SettingView.kt @@ -8,8 +8,8 @@ import be.simplenotes.views.extensions.summary import io.konform.validation.ValidationError import kotlinx.html.* import kotlinx.html.ButtonType.submit -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class SettingView(@Named("styles") styles: String) : View(styles) { @@ -20,7 +20,6 @@ class SettingView(@Named("styles") styles: String) : View(styles) { validationErrors: List = emptyList(), ) = renderPage("Settings", loggedInUser = loggedInUser) { div("container mx-auto") { - section("m-4 p-4 bg-gray-800 rounded") { h1("text-xl") { +"Welcome " @@ -29,17 +28,15 @@ class SettingView(@Named("styles") styles: String) : View(styles) { } section("m-4 p-2 bg-gray-800 rounded flex flex-wrap justify-around items-end") { - form(classes = "m-2", method = FormMethod.post, action = "/export") { button( name = "display", classes = "inline btn btn-teal block", - type = submit + type = submit, ) { +"Display my data" } } form(classes = "m-2", method = FormMethod.post, action = "/export") { - div { listOf("json", "zip").forEach { format -> radioInput(name = "format") { @@ -69,7 +66,6 @@ class SettingView(@Named("styles") styles: String) : View(styles) { error?.let { alert(Alert.Warning, error) } details { - if (error != null || validationErrors.isNotEmpty()) { attributes["open"] = "" } @@ -87,7 +83,7 @@ class SettingView(@Named("styles") styles: String) : View(styles) { placeholder = "Password", autoComplete = "off", type = InputType.password, - error = validationErrors.find { it.dataPath == ".password" }?.message + error = validationErrors.find { it.dataPath == ".password" }?.message, ) checkBoxInput(name = "checked") { id = "checked" @@ -100,7 +96,7 @@ class SettingView(@Named("styles") styles: String) : View(styles) { button( type = submit, classes = "block mt-4 btn btn-red", - name = "delete" + name = "delete", ) { +"I'm sure" } } } diff --git a/views/src/UserView.kt b/views/src/UserView.kt index c6661a2..ac5e91c 100644 --- a/views/src/UserView.kt +++ b/views/src/UserView.kt @@ -7,8 +7,8 @@ import be.simplenotes.views.components.input import be.simplenotes.views.components.submitButton import io.konform.validation.ValidationError import kotlinx.html.* -import javax.inject.Named -import javax.inject.Singleton +import jakarta.inject.Named +import jakarta.inject.Singleton @Singleton class UserView(@Named("styles") styles: String) : View(styles) { @@ -23,7 +23,7 @@ class UserView(@Named("styles") styles: String) : View(styles) { error, validationErrors, "Create an account", - "Register" + "Register", ) { +"Already have an account? " a(href = "/login", classes = "no-underline text-blue-500 hover:text-blue-400 font-bold") { +"Sign In" } @@ -63,14 +63,14 @@ class UserView(@Named("styles") styles: String) : View(styles) { id = "username", placeholder = "Username", autoComplete = "username", - error = validationErrors.find { it.dataPath == ".username" }?.message + error = validationErrors.find { it.dataPath == ".username" }?.message, ) input( id = "password", placeholder = "Password", autoComplete = "new-password", type = InputType.password, - error = validationErrors.find { it.dataPath == ".password" }?.message + error = validationErrors.find { it.dataPath == ".password" }?.message, ) submitButton(submit) } diff --git a/views/src/components/Forms.kt b/views/src/components/Forms.kt index 92d6df5..ef89d04 100644 --- a/views/src/components/Forms.kt +++ b/views/src/components/Forms.kt @@ -8,13 +8,13 @@ internal fun FlowContent.input( placeholder: String, id: String, autoComplete: String? = null, - error: String? = null + error: String? = null, ) { val colors = "bg-gray-800 border-gray-700 focus:border-teal-500 text-white" div("mb-8") { input( type = type, - classes = "$colors rounded w-full border appearance-none focus:outline-none text-base p-2" + classes = "$colors rounded w-full border appearance-none focus:outline-none text-base p-2", ) { attributes["placeholder"] = placeholder attributes["aria-label"] = placeholder @@ -30,7 +30,7 @@ internal fun FlowContent.submitButton(text: String) { div("flex items-center mt-6") { button( type = submit, - classes = "btn btn-teal w-full" + classes = "btn btn-teal w-full", ) { +text } } } diff --git a/views/src/components/NoteListHeader.kt b/views/src/components/NoteListHeader.kt index 682f054..0ce39f5 100644 --- a/views/src/components/NoteListHeader.kt +++ b/views/src/components/NoteListHeader.kt @@ -10,11 +10,11 @@ internal fun DIV.noteListHeader(numberOfDeletedNotes: Int, query: String = "") { span { a( href = "/notes/trash", - classes = "btn btn-teal" + classes = "btn btn-teal", ) { +"Trash ($numberOfDeletedNotes)" } a( href = "/notes/new", - classes = "ml-2 btn btn-green" + classes = "ml-2 btn btn-green", ) { +"New" } } } diff --git a/views/src/extensions/KotlinxHtmlExtensions.kt b/views/src/extensions/KotlinxHtmlExtensions.kt index 4bb9323..4a88834 100644 --- a/views/src/extensions/KotlinxHtmlExtensions.kt +++ b/views/src/extensions/KotlinxHtmlExtensions.kt @@ -8,7 +8,7 @@ internal class SUMMARY(consumer: TagConsumer<*>) : consumer, emptyMap(), inlineTag = true, - emptyTag = false + emptyTag = false, ), HtmlInlineTag diff --git a/views/src/utils/PrettyDate.kt b/views/src/utils/PrettyDate.kt index 9b89a74..2558777 100644 --- a/views/src/utils/PrettyDate.kt +++ b/views/src/utils/PrettyDate.kt @@ -8,5 +8,5 @@ import java.util.* private val prettyTime = PrettyTime() internal fun LocalDateTime.toTimeAgo(): String = prettyTime.format( - Date.from(atZone(ZoneId.systemDefault()).toInstant()) + Date.from(atZone(ZoneId.systemDefault()).toInstant()), )