Update kotlin libs

This commit is contained in:
2023-05-09 22:40:10 +02:00
parent 5aa2e80c5f
commit f2bdc8d6c7
66 changed files with 261 additions and 264 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies {
implementation(project(":persistence"))
implementation(project(":search"))
api(libs.arrow.core.data)
api(libs.arrow.core)
api(libs.konform)
micronaut()
+1 -1
View File
@@ -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
+15 -18
View File
@@ -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<MarkdownParsingError, Document>
}
private typealias MetaMdPair = Pair<String, String>
@Singleton
internal class MarkdownServiceImpl(
private val parser: Parser,
private val renderer: HtmlRenderer,
) : MarkdownService {
private val yamlBoundPattern = "-{3}".toRegex()
private fun splitMetaFromDocument(input: String): Either<MarkdownParsingError.MissingMeta, MetaMdPair> {
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<MarkdownParsingError.InvalidMeta, NoteMetadata> {
private fun parseMeta(input: String) = either {
val load: Map<String, Any> = 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<MarkdownParsingError, Document> {
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)
}
+8 -9
View File
@@ -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<MarkdownParsingError, PersistedNote> {
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<MarkdownParsingError, PersistedNote?> {
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) }
+22 -28
View File
@@ -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<RegisterError, PersistedUser>
@@ -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<LoginError, Token> {
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<DeleteError, Unit> {
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)
}
}
+3 -3
View File
@@ -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,
"""<input type="checkbox" checked="checked" disabled="disabled" readonly="readonly" />&nbsp;"""
"""<input type="checkbox" checked="checked" disabled="disabled" readonly="readonly" />&nbsp;""",
)
set(
TaskListExtension.ITEM_NOT_DONE_MARKER,
"""<input type="checkbox" disabled="disabled" readonly="readonly" />&nbsp;"""
"""<input type="checkbox" disabled="disabled" readonly="readonly" />&nbsp;""",
)
set(HtmlRenderer.SOFT_BREAK, "<br>")
}
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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<T> {
fun extract(decodedJWT: DecodedJWT): T?
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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<T>(jwtConfig: JwtConfig, private val mapper: JwtMapper<T>) {
+1 -1
View File
@@ -89,6 +89,6 @@ internal fun parseSearchTerms(input: String): SearchTerms {
title = title,
tag = tag,
content = content,
all = all
all = all,
)
}
+11 -12
View File
@@ -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<LoginError.InvalidLoginForm, User> {
fun validateLogin(form: LoginForm) = either<LoginError.InvalidLoginForm, User> {
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<RegisterError.InvalidRegisterForm, User> {
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<DeleteForm> {
@@ -47,9 +46,9 @@ internal object UserValidations {
}
}
fun validateDelete(form: DeleteForm): Either<DeleteError.InvalidForm, User> {
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!!)
}
}
@@ -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")
+2 -2
View File
@@ -21,7 +21,7 @@ fun isRight() = object : Matcher<Either<*, *>> {
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 <reified A> isLeftOfType() = object : Matcher<Either<*, *>> {
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}>")
}
+2 -2
View File
@@ -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",
),
)
@@ -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<LoginForm> = 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<RegisterForm> = 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