Compare commits

...

7 Commits

52 changed files with 195 additions and 143 deletions

10
.gitignore vendored
View File

@ -1,12 +1,3 @@
# Java
.mtj.tmp/
*.class
*.jar
*.war
*.ear
*.nar
hs_err_pid*
# Gradle
build/
.gradle
@ -52,3 +43,4 @@ jspm_packages/
# python
__pycache__

View File

@ -24,4 +24,17 @@ COPY --from=jdkbuilder /myjdk /myjdk
COPY simplenotes-app/build/libs/app-*-all.jar /app/simplenotes.jar
WORKDIR /app
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms64m", "-Xmx256m", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "simplenotes.jar"]
CMD [ \
"/myjdk/bin/java", \
"--add-opens", \
"java.base/java.nio=ALL-UNNAMED", \
"-server", \
"-XX:+UnlockExperimentalVMOptions", \
"-Xms64m", \
"-Xmx256m", \
"-XX:+UseG1GC", \
"-XX:MaxGCPauseMillis=100", \
"-XX:+UseStringDeduplication", \
"-jar", \
"simplenotes.jar" \
]

View File

@ -1,7 +1,8 @@
plugins {
`kotlin-dsl`
kotlin("jvm") version "1.4.10"
id("com.github.johnrengelman.shadow") version "6.1.0"
id("com.github.johnrengelman.shadow") version "6.1.0" apply false
kotlin("plugin.serialization") version "1.4.10"
}
repositories {
@ -11,5 +12,7 @@ repositories {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10")
implementation("org.jetbrains.kotlin:kotlin-serialization:1.4.10")
implementation("com.github.jengelman.gradle.plugins:shadow:6.1.0")
implementation("org.jlleitschuh.gradle:ktlint-gradle:9.4.1")
}

View File

@ -2,9 +2,8 @@ package be.simplenotes
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.getByType
import java.io.File
import java.lang.ProcessBuilder.Redirect.PIPE
import java.util.concurrent.TimeUnit
@ -12,31 +11,57 @@ import kotlin.concurrent.thread
open class CssTask : DefaultTask() {
private val root = project.file(".").parent
private val root = project.parent!!.rootDir
private val viewsProject = project
.parent
?.project(":simplenotes-views")
?: error("Missing :simplenotes-views")
@get:InputDirectory
val templatesDir = File(root, "simplenotes-views/src/main/kotlin/be/simplenotes/views")
val templatesDir = viewsProject.extensions
.getByType<SourceSetContainer>()
.asMap.getOrElse("main") { error("main sources not found") }
.allSource.srcDirs
.find { it.endsWith("kotlin") }
?: error("kotlin sources not found")
private val yarnRoot = File(project.rootDir, "css")
@get:InputDirectory
val postCssDir = File(project.rootDir, "css/src")
@get:InputFiles
val postCssConfig = listOf(
"tailwind.config.js",
"postcss.config.js",
"package.json"
).map { File(yarnRoot, it) }
@get:OutputDirectory
val outputRootDir = File(project.buildDir, "generated-resources/css")
private val yarnRoot = File(project.rootDir, "css")
private val cssIndex = File(postCssDir, "styles.pcss")
private val cssOutput = File(outputRootDir, "static/styles.css")
private val manifestOutput = File(outputRootDir, "css-manifest.json")
private val purgeGlob = "$templatesDir/**/*.kt"
@TaskAction
fun generateCss() {
// TODO: auto yarn install ?
outputRootDir.listFiles()?.let { it.forEach { it.delete() } }
outputRootDir.deleteRecursively()
ProcessBuilder("yarn", "run", "postcss", "build", "$cssIndex", "--output", "$cssOutput")
.apply { environment()["MANIFEST"] = "$manifestOutput" }
.apply {
environment().let {
it["MANIFEST"] = "$manifestOutput"
it["NODE_ENV"] = "production"
it["PURGE"] = purgeGlob
}
}
.redirectOutput(PIPE)
.redirectError(PIPE)
.directory(yarnRoot)

View File

@ -3,7 +3,7 @@
package be.simplenotes
object Libs {
const val arrowCore = "io.arrow-kt:arrow-core:0.11.0"
const val arrowCoreData = "io.arrow-kt:arrow-core-data:0.11.0"
const val commonsCompress = "org.apache.commons:commons-compress:1.20"
const val flexmark = "com.vladsch.flexmark:flexmark:0.62.2"
const val flexmarkGfmTasklist = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2"

View File

@ -6,6 +6,7 @@ plugins {
java
kotlin("jvm")
`java-library`
id("org.jlleitschuh.gradle.ktlint")
}
repositories {
@ -21,6 +22,7 @@ version = "1.0-SNAPSHOT"
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.4.10"))
}
tasks.withType<Test> {

View File

@ -0,0 +1,6 @@
package be.simplenotes
plugins {
kotlin("jvm") apply false
kotlin("plugin.serialization")
}

View File

@ -1,4 +1,3 @@
arrow/core/extensions/**
org/checkerframework/**
org/intellij/**
com/google/errorprone/**

View File

@ -1,7 +1,7 @@
module.exports = {
purge: {
content: [
'../simplenotes-app/src/main/kotlin/be/simplenotes/app/views/**/*.kt'
process.env.PURGE
]
},
theme: {

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -2,6 +2,7 @@ import be.simplenotes.Libs
plugins {
id("be.simplenotes.base")
id("be.simplenotes.kotlinx-serialization")
id("be.simplenotes.app-shadow")
id("be.simplenotes.app-css")
id("be.simplenotes.app-docker")
@ -16,7 +17,7 @@ dependencies {
implementation(project(":simplenotes-views"))
implementation(Libs.koinCore)
implementation(Libs.arrowCore)
implementation(Libs.arrowCoreData)
implementation(Libs.konform)
implementation(Libs.http4kCore)
implementation(Libs.jettyServer)

View File

@ -2,10 +2,10 @@ package be.simplenotes.app.api
import be.simplenotes.app.extensions.auto
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import be.simplenotes.domain.usecases.NoteService
import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -28,7 +28,7 @@ class ApiNoteController(private val noteService: NoteService, private val json:
)
}
fun notes(request: Request, loggedInUser: LoggedInUser): Response {
fun notes(@Suppress("UNUSED_PARAMETER") request: Request, loggedInUser: LoggedInUser): Response {
val notes = noteService.paginatedNotes(loggedInUser.userId, page = 1).notes
return persistedNotesMetadataLens(notes, Response(OK))
}
@ -40,12 +40,15 @@ class ApiNoteController(private val noteService: NoteService, private val json:
fun update(request: Request, loggedInUser: LoggedInUser): Response {
val content = noteContentLens(request)
return noteService.update(loggedInUser.userId, uuidLens(request), content).fold({
Response(BAD_REQUEST)
}, {
if (it == null) Response(NOT_FOUND)
else Response(OK)
})
return noteService.update(loggedInUser.userId, uuidLens(request), content).fold(
{
Response(BAD_REQUEST)
},
{
if (it == null) Response(NOT_FOUND)
else Response(OK)
}
)
}
fun search(request: Request, loggedInUser: LoggedInUser): Response {
@ -61,7 +64,6 @@ class ApiNoteController(private val noteService: NoteService, private val json:
private val persistedNotesMetadataLens = json.auto<List<PersistedNoteMetadata>>().toLens()
private val persistedNoteLens = json.auto<PersistedNote>().toLens()
private val uuidLens = Path.uuid().of("uuid")
}
@Serializable

View File

@ -7,6 +7,6 @@ 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) =
fun healthCheck(@Suppress("UNUSED_PARAMETER") request: Request) =
if (dbHealthCheck.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
}

View File

@ -3,12 +3,12 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.redirect
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.views.NoteView
import be.simplenotes.domain.usecases.NoteService
import be.simplenotes.domain.usecases.markdown.InvalidMeta
import be.simplenotes.domain.usecases.markdown.MissingMeta
import be.simplenotes.domain.usecases.markdown.ValidationError
import be.simplenotes.types.LoggedInUser
import be.simplenotes.views.NoteView
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
@ -33,8 +33,16 @@ class NoteController(
return noteService.create(loggedInUser.userId, markdownForm).fold(
{
val html = when (it) {
MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
MissingMeta -> view.noteEditor(
loggedInUser,
error = "Missing note metadata",
textarea = markdownForm
)
InvalidMeta -> view.noteEditor(
loggedInUser,
error = "Invalid note metadata",
textarea = markdownForm
)
is ValidationError -> view.noteEditor(
loggedInUser,
validationErrors = it.validationErrors,
@ -105,8 +113,16 @@ class NoteController(
return noteService.update(loggedInUser.userId, note.uuid, markdownForm).fold(
{
val html = when (it) {
MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
MissingMeta -> view.noteEditor(
loggedInUser,
error = "Missing note metadata",
textarea = markdownForm
)
InvalidMeta -> view.noteEditor(
loggedInUser,
error = "Invalid note metadata",
textarea = markdownForm
)
is ValidationError -> view.noteEditor(
loggedInUser,
validationErrors = it.validationErrors,

View File

@ -2,11 +2,11 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.redirect
import be.simplenotes.views.SettingView
import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.delete.DeleteError
import be.simplenotes.domain.usecases.users.delete.DeleteForm
import be.simplenotes.types.LoggedInUser
import be.simplenotes.views.SettingView
import org.http4k.core.*
import org.http4k.core.body.form
import org.http4k.core.cookie.invalidateCookie
@ -67,7 +67,10 @@ class SettingsController(
Response(Status.OK)
.with(attachment("$filename.json", "application/json"))
.body(userService.exportAsJson(loggedInUser.userId))
} else Response(Status.OK).body(userService.exportAsJson(loggedInUser.userId)).header("Content-Type", "application/json")
} else Response(Status.OK).body(userService.exportAsJson(loggedInUser.userId)).header(
"Content-Type",
"application/json"
)
}
private fun Request.deleteForm(loggedInUser: LoggedInUser) =

View File

@ -3,14 +3,14 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.isSecure
import be.simplenotes.app.extensions.redirect
import be.simplenotes.views.UserView
import be.simplenotes.config.JwtConfig
import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.login.*
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
import be.simplenotes.domain.usecases.users.register.RegisterForm
import be.simplenotes.domain.usecases.users.register.UserExists
import be.simplenotes.config.JwtConfig
import be.simplenotes.types.LoggedInUser
import be.simplenotes.views.UserView
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response

View File

@ -23,7 +23,8 @@ 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
ContentType.APPLICATION_JSON.withNoDirectives(),
ContentNegotiation.StrictNoDirective
).map(
{ it.payload.asString() },
{ Body(it) }

View File

@ -25,7 +25,7 @@ class AuthFilter(
JwtSource.Header -> it.bearerTokenHeader()
JwtSource.Cookie -> it.bearerTokenCookie()
}
val jwtPayload = token?.let { token -> extractor(token) }
val jwtPayload = token?.let { extractor(token) }
when {
jwtPayload != null -> {
ctx[it][authKey] = jwtPayload

View File

@ -12,9 +12,12 @@ import org.http4k.servlet.asServlet
class Jetty(private val port: Int, private val server: Server) : ServerConfig {
constructor(port: Int = 8000) : this(port, http(port))
constructor(port: Int, vararg inConnectors: ConnectorBuilder) : this(port, Server().apply {
inConnectors.forEach { addConnector(it(this)) }
})
constructor(port: Int, vararg inConnectors: ConnectorBuilder) : this(
port,
Server().apply {
inConnectors.forEach { addConnector(it(this)) }
}
)
override fun toServer(httpHandler: HttpHandler): Http4kServer {
server.insertHandler(httpHandler.toJettyHandler())

View File

@ -10,7 +10,6 @@ import be.simplenotes.app.jetty.Jetty
import be.simplenotes.app.routes.Router
import be.simplenotes.app.utils.StaticFileResolver
import be.simplenotes.app.utils.StaticFileResolverImpl
import be.simplenotes.views.ErrorView
import be.simplenotes.config.ServerConfig
import org.eclipse.jetty.server.ServerConnector
import org.http4k.core.Filter

View File

@ -10,7 +10,7 @@ import java.util.*
internal class UuidSerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.toString())

View File

@ -1,8 +1,8 @@
package be.simplenotes.app.filters
import be.simplenotes.config.JwtConfig
import be.simplenotes.domain.security.JwtPayloadExtractor
import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.config.JwtConfig
import be.simplenotes.types.LoggedInUser
import com.natpryce.hamkrest.assertion.assertThat
import org.http4k.core.*

View File

@ -30,7 +30,9 @@ internal class SearchTermsParserKtTest {
createResult("tag:'example' title:'other' end", title = "other", tag = "example", all = "end"),
createResult(
"tag:'example abc' title:'other with words' this is the end ",
title = "other with words", tag = "example abc", all = "this is the end"
title = "other with words",
tag = "example abc",
all = "this is the end"
),
)

View File

@ -2,7 +2,7 @@ import be.simplenotes.Libs
plugins {
id("be.simplenotes.base")
kotlin("plugin.serialization") version "1.4.10"
id("be.simplenotes.kotlinx-serialization")
}
dependencies {
@ -13,7 +13,7 @@ dependencies {
implementation(Libs.kotlinxSerializationJson)
implementation(Libs.koinCore)
implementation(Libs.arrowCore)
implementation(Libs.arrowCoreData)
implementation(Libs.konform)
implementation(Libs.jbcrypt)
implementation(Libs.javaJwt)

View File

@ -1,7 +1,7 @@
package be.simplenotes.domain.usecases.export
import be.simplenotes.types.ExportedNote
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.types.ExportedNote
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
@ -31,7 +31,6 @@ internal class ExportUseCaseImpl(private val noteRepository: NoteRepository, pri
}
}
class ZipOutput : AutoCloseable {
val outputStream = ByteArrayOutputStream()
private val zipOutputStream = ZipArchiveOutputStream(outputStream)

View File

@ -12,14 +12,15 @@ internal class DeleteUseCaseImpl(
private val userRepository: UserRepository,
private val passwordHash: PasswordHash,
private val searcher: NoteSearcher,
) : DeleteUseCase {
) : DeleteUseCase {
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 },
{ Unit })
{ Unit }
)
!Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { Unit })
searcher.dropIndex(persistedUser.id)
}

View File

@ -3,10 +3,10 @@ package be.simplenotes.domain.usecases.users.register
import arrow.core.Either
import arrow.core.filterOrElse
import arrow.core.leftIfNull
import be.simplenotes.types.PersistedUser
import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
internal class RegisterUseCaseImpl(
private val userRepository: UserRepository,

View File

@ -1,8 +1,8 @@
package be.simplenotes.domain.usecases.users.register
import arrow.core.Either
import be.simplenotes.types.PersistedUser
import be.simplenotes.domain.usecases.users.login.LoginForm
import be.simplenotes.types.PersistedUser
import io.konform.validation.ValidationErrors
sealed class RegisterError

View File

@ -3,13 +3,13 @@ package be.simplenotes.domain.validation
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import be.simplenotes.types.User
import be.simplenotes.domain.usecases.users.delete.DeleteError
import be.simplenotes.domain.usecases.users.delete.DeleteForm
import be.simplenotes.domain.usecases.users.login.InvalidLoginForm
import be.simplenotes.domain.usecases.users.login.LoginForm
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
import be.simplenotes.domain.usecases.users.register.RegisterForm
import be.simplenotes.types.User
import io.konform.validation.Validation
import io.konform.validation.jsonschema.maxLength
import io.konform.validation.jsonschema.minLength

View File

@ -1,7 +1,7 @@
package be.simplenotes.domain.security
import be.simplenotes.domain.usecases.users.login.Token
import be.simplenotes.config.JwtConfig
import be.simplenotes.domain.usecases.users.login.Token
import be.simplenotes.types.LoggedInUser
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm

View File

@ -1,12 +1,12 @@
package be.simplenotes.domain.usecases.users.login
import be.simplenotes.types.PersistedUser
import be.simplenotes.config.JwtConfig
import be.simplenotes.domain.security.BcryptPasswordHash
import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.config.JwtConfig
import be.simplenotes.domain.testutils.isLeftOfType
import be.simplenotes.domain.testutils.isRight
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
import com.natpryce.hamkrest.assertion.assertThat
import io.mockk.*
import org.junit.jupiter.api.BeforeEach

View File

@ -1,10 +1,10 @@
package be.simplenotes.domain.usecases.users.register
import be.simplenotes.types.PersistedUser
import be.simplenotes.domain.security.BcryptPasswordHash
import be.simplenotes.domain.testutils.isLeftOfType
import be.simplenotes.domain.testutils.isRight
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import io.mockk.*

View File

@ -1,8 +1,8 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import be.simplenotes.config.DataSourceConfig
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.database.asIterable
import me.liuwj.ktorm.database.use

View File

@ -1,8 +1,8 @@
package be.simplenotes.persistance
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.utils.DbType
import be.simplenotes.persistance.utils.type
import be.simplenotes.config.DataSourceConfig
import org.flywaydb.core.Flyway
import javax.sql.DataSource

View File

@ -10,7 +10,6 @@ import org.mapstruct.ReportingPolicy
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)
@ -35,7 +34,11 @@ internal abstract class NoteConverter {
fun toPersistedNote(entity: NoteEntity, tags: Tags) = PersistedNote(
NoteMetadata(title = entity.title, tags = tags),
entity.markdown, entity.html, entity.updatedAt, entity.uuid, entity.public
entity.markdown,
entity.html,
entity.updatedAt,
entity.uuid,
entity.public
)
@Mappings(
@ -46,15 +49,15 @@ internal abstract class NoteConverter {
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
}
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"),
@ -73,7 +76,6 @@ internal abstract class NoteConverter {
@Mapping(target = "deleted", source = "trash")
abstract fun toEntity(exportedNote: ExportedNote): NoteEntity
}
typealias Tags = List<String>

View File

@ -1,11 +1,11 @@
package be.simplenotes.persistance.notes
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.types.ExportedNote
import be.simplenotes.types.Note
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.repositories.NoteRepository
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*
@ -211,5 +211,4 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
.filter { it.noteUuid inList map { note -> note.uuid } }
.groupByTo(HashMap(), { it.note.uuid }, { it.name })
}
}

View File

@ -1,9 +1,9 @@
package be.simplenotes.persistance.users
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import be.simplenotes.persistance.converters.UserConverter
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.any

View File

@ -26,10 +26,14 @@ internal class NoteConverterTest {
}
val tags = listOf("a", "b")
val note = converter.toNote(entity, tags)
val expectedNote = Note(NoteMetadata(
title = "title",
tags = tags,
), markdown = "md", html = "html")
val expectedNote = Note(
NoteMetadata(
title = "title",
tags = tags,
),
markdown = "md",
html = "html"
)
assertThat(note).isEqualTo(expectedNote)
}
@ -77,7 +81,6 @@ internal class NoteConverterTest {
)
assertThat(note).isEqualTo(expectedNote)
}
}
@Nested
@ -162,7 +165,5 @@ internal class NoteConverterTest {
.hasFieldOrPropertyWithValue("updatedAt", exportedNote.updatedAt)
.hasFieldOrPropertyWithValue("deleted", true)
}
}
}

View File

@ -1,8 +1,8 @@
package be.simplenotes.persistance.converters
import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.types.PersistedUser
import be.simplenotes.types.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

View File

@ -1,14 +1,13 @@
package be.simplenotes.persistance.notes
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.DbMigrations
import be.simplenotes.persistance.converters.NoteConverter
import be.simplenotes.persistance.migrationModule
import be.simplenotes.persistance.persistanceModule
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.*
import be.simplenotes.types.*
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.eq
import me.liuwj.ktorm.entity.filter

View File

@ -1,11 +1,11 @@
package be.simplenotes.persistance.users
import be.simplenotes.types.User
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.DbMigrations
import be.simplenotes.persistance.migrationModule
import be.simplenotes.persistance.persistanceModule
import be.simplenotes.config.DataSourceConfig
import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.types.User
import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.*

View File

@ -1,7 +1,6 @@
package be.simplenotes.search
import be.simplenotes.types.PersistedNote
import be.simplenotes.search.utils.rmdir
import org.apache.lucene.analysis.standard.StandardAnalyzer
import org.apache.lucene.document.Document
import org.apache.lucene.index.*
@ -90,7 +89,11 @@ internal class NoteSearcherImpl(basePath: Path = Path.of("/tmp", "lucene")) : No
emptyList()
}
override fun dropIndex(userId: Int) = rmdir(File(baseFile, userId.toString()).toPath())
override fun dropIndex(userId: Int) {
File(baseFile, userId.toString()).deleteRecursively()
}
override fun dropAll() = rmdir(baseFile.toPath())
override fun dropAll() {
baseFile.deleteRecursively()
}
}

View File

@ -1,29 +0,0 @@
package be.simplenotes.search.utils
import java.io.IOException
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
internal fun rmdir(path: Path) {
try {
Files.walkFileTree(
path,
object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes?): FileVisitResult {
Files.delete(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
Files.delete(dir)
return FileVisitResult.CONTINUE
}
}
)
} catch (e: IOException) {
// This is fine
}
}

View File

@ -24,12 +24,14 @@ internal class NoteSearcherImplTest {
content: String = "",
uuid: UUID = UUID.randomUUID(),
): PersistedNote {
val note = PersistedNote(NoteMetadata(title, tags),
val note = PersistedNote(
NoteMetadata(title, tags),
markdown = content,
html = "",
LocalDateTime.MIN,
uuid,
public = false)
public = false
)
searcher.indexNote(1, note)
return note
}

View File

@ -2,7 +2,7 @@ import be.simplenotes.Libs
plugins {
id("be.simplenotes.base")
kotlin("plugin.serialization") version "1.4.10"
id("be.simplenotes.kotlinx-serialization")
}
dependencies {

View File

@ -1,7 +1,6 @@
package be.simplenotes.views
import be.simplenotes.types.LoggedInUser
import be.simplenotes.views.components.noteListHeader
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
import be.simplenotes.views.components.*
@ -38,7 +37,9 @@ class NoteView(styles: String) : View(styles) {
|tags: []
|---
|
""".trimMargin("|")
""".trimMargin(
"|"
)
}
submitButton("Save")
}
@ -128,7 +129,7 @@ class NoteView(styles: String) : View(styles) {
+"You are viewing a public note "
}
hr { }
hr { }
}
div("flex items-center justify-between mb-4") {
@ -151,7 +152,7 @@ class NoteView(styles: String) : View(styles) {
+"/notes/public/${note.uuid}"
}
}
hr { }
hr { }
}
}
@ -182,7 +183,8 @@ class NoteView(styles: String) : View(styles) {
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"}" +
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"
) {
+"Public"

View File

@ -28,9 +28,11 @@ class SettingView(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",
button(
name = "display",
classes = "inline btn btn-teal block",
type = submit) { +"Display my data" }
type = submit
) { +"Display my data" }
}
form(classes = "m-2", method = FormMethod.post, action = "/export") {

View File

@ -22,7 +22,7 @@ abstract class View(private val styles: String) {
meta(name = "viewport", content = "width=device-width, initial-scale=1")
title("$title - SimpleNotes")
description?.let { meta(name = "description", content = it) }
link(rel = "preload", href = "/recursive-0.0.1.woff2"){
link(rel = "preload", href = "/recursive-0.0.1.woff2") {
attributes["as"] = "font"
attributes["type"] = "font/woff2"
attributes["crossorigin"] = "anonymous"

View File

@ -4,7 +4,9 @@ import kotlinx.html.*
internal class SUMMARY(consumer: TagConsumer<*>) :
HTMLTag(
"summary", consumer, emptyMap(),
"summary",
consumer,
emptyMap(),
inlineTag = true,
emptyTag = false
),

View File

@ -7,4 +7,6 @@ import java.util.*
private val prettyTime = PrettyTime()
internal fun LocalDateTime.toTimeAgo(): String = prettyTime.format(Date.from(atZone(ZoneId.systemDefault()).toInstant()))
internal fun LocalDateTime.toTimeAgo(): String = prettyTime.format(
Date.from(atZone(ZoneId.systemDefault()).toInstant())
)