Compare commits
24 Commits
ad97ba029e
...
jigsaw
| Author | SHA1 | Date | |
|---|---|---|---|
| e5a2b8993f | |||
| 38750a588c | |||
| ee026ec829 | |||
| 29b024d360 | |||
| cd12d1561a | |||
| c2eaf3d0cc | |||
| 4c9ac8944e | |||
| 4ff97044f0 | |||
| ead1932d48 | |||
| 4a7dcec363 | |||
| cb76a3253d | |||
| 681fd635b3 | |||
| 9467db2382 | |||
| 7ed3494808 | |||
| ceb310bf02 | |||
| 2c3106c5c1 | |||
| 4effa8231a | |||
| b78420e106 | |||
| dd08763161 | |||
| e0b1514965 | |||
| 69c91ec86a | |||
| 1bc45461c3 | |||
| 0dfb2a7e03 | |||
| a7c8e63b11 |
@@ -125,8 +125,8 @@ data/
|
|||||||
letsencrypt/
|
letsencrypt/
|
||||||
|
|
||||||
# generated resources
|
# generated resources
|
||||||
app/src/main/resources/css-manifest.json
|
simplenotes-app/src/main/resources/css-manifest.json
|
||||||
app/src/main/resources/static/styles*
|
simplenotes-app/src/main/resources/static/styles*
|
||||||
|
|
||||||
# h2 db
|
# h2 db
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -4,19 +4,23 @@ WORKDIR /tmp
|
|||||||
|
|
||||||
# Cache dependencies
|
# Cache dependencies
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
COPY app/pom.xml app/pom.xml
|
COPY simplenotes-test-resources/pom.xml simplenotes-test-resources/pom.xml
|
||||||
COPY domain/pom.xml domain/pom.xml
|
COPY simplenotes-types/pom.xml simplenotes-types/pom.xml
|
||||||
COPY persistance/pom.xml persistance/pom.xml
|
COPY simplenotes-config/pom.xml simplenotes-config/pom.xml
|
||||||
COPY shared/pom.xml shared/pom.xml
|
COPY simplenotes-persistance/pom.xml simplenotes-persistance/pom.xml
|
||||||
COPY search/pom.xml search/pom.xml
|
COPY simplenotes-search/pom.xml simplenotes-search/pom.xml
|
||||||
|
COPY simplenotes-domain/pom.xml simplenotes-domain/pom.xml
|
||||||
|
COPY simplenotes-app/pom.xml simplenotes-app/pom.xml
|
||||||
|
|
||||||
RUN mvn verify clean --fail-never
|
RUN mvn verify clean --fail-never
|
||||||
|
|
||||||
COPY app/src app/src
|
COPY simplenotes-test-resources/src simplenotes-test-resources/src
|
||||||
COPY domain/src domain/src
|
COPY simplenotes-types/src simplenotes-types/src
|
||||||
COPY persistance/src persistance/src
|
COPY simplenotes-config/src simplenotes-config/src
|
||||||
COPY shared/src shared/src
|
COPY simplenotes-persistance/src simplenotes-persistance/src
|
||||||
COPY search/src search/src
|
COPY simplenotes-search/src simplenotes-search/src
|
||||||
|
COPY simplenotes-domain/src simplenotes-domain/src
|
||||||
|
COPY simplenotes-app/src simplenotes-app/src
|
||||||
|
|
||||||
RUN mvn -Dstyle.color=always package
|
RUN mvn -Dstyle.color=always package
|
||||||
|
|
||||||
@@ -32,6 +36,8 @@ RUN strip -p --strip-unneeded /myjdk/lib/server/libjvm.so
|
|||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
ENV APPLICATION_USER simplenotes
|
ENV APPLICATION_USER simplenotes
|
||||||
RUN adduser -D -g '' $APPLICATION_USER
|
RUN adduser -D -g '' $APPLICATION_USER
|
||||||
|
|
||||||
@@ -40,8 +46,8 @@ RUN chown -R $APPLICATION_USER /app
|
|||||||
|
|
||||||
USER $APPLICATION_USER
|
USER $APPLICATION_USER
|
||||||
|
|
||||||
COPY --from=builder /tmp/app/target/app-*.jar /app/app.jar
|
COPY --from=builder /tmp/simplenotes-app/target/simplenotes-app-*.jar /app/simplenotes.jar
|
||||||
COPY --from=jdkbuilder /myjdk /myjdk
|
COPY --from=jdkbuilder /myjdk /myjdk
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms256m", "-Xmx1g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "app.jar"]
|
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms64m", "-Xmx256m", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "simplenotes.jar"]
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package be.simplenotes.app
|
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Server
|
|
||||||
import org.eclipse.jetty.server.ServerConnector
|
|
||||||
import org.http4k.routing.RoutingHttpHandler
|
|
||||||
import org.http4k.server.ConnectorBuilder
|
|
||||||
import org.http4k.server.Jetty
|
|
||||||
import org.http4k.server.ServerConfig
|
|
||||||
import org.http4k.server.asServer
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import be.simplenotes.shared.config.ServerConfig as SimpleNotesServeConfig
|
|
||||||
|
|
||||||
class Server(
|
|
||||||
private val config: SimpleNotesServeConfig,
|
|
||||||
private val serverConfig: ServerConfig,
|
|
||||||
private val router: RoutingHttpHandler,
|
|
||||||
) {
|
|
||||||
fun start() {
|
|
||||||
router.asServer(serverConfig).start()
|
|
||||||
LoggerFactory.getLogger(javaClass).info("Listening on http://${config.host}:${config.port}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serverConfig(config: SimpleNotesServeConfig): ServerConfig {
|
|
||||||
val builder: ConnectorBuilder = { server: Server ->
|
|
||||||
ServerConnector(server).apply {
|
|
||||||
port = config.port
|
|
||||||
host = config.host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Jetty(config.port, builder)
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
package be.simplenotes.app
|
|
||||||
|
|
||||||
import be.simplenotes.app.api.ApiNoteController
|
|
||||||
import be.simplenotes.app.api.ApiUserController
|
|
||||||
import be.simplenotes.app.controllers.BaseController
|
|
||||||
import be.simplenotes.app.controllers.NoteController
|
|
||||||
import be.simplenotes.app.controllers.SettingsController
|
|
||||||
import be.simplenotes.app.controllers.UserController
|
|
||||||
import be.simplenotes.app.filters.AuthFilter
|
|
||||||
import be.simplenotes.app.filters.AuthType
|
|
||||||
import be.simplenotes.app.filters.ErrorFilter
|
|
||||||
import be.simplenotes.app.filters.JwtSource
|
|
||||||
import be.simplenotes.app.routes.Router
|
|
||||||
import be.simplenotes.app.utils.StaticFileResolver
|
|
||||||
import be.simplenotes.app.utils.StaticFileResolverImpl
|
|
||||||
import be.simplenotes.app.views.*
|
|
||||||
import be.simplenotes.domain.domainModule
|
|
||||||
import be.simplenotes.domain.usecases.NoteService
|
|
||||||
import be.simplenotes.persistance.DbMigrations
|
|
||||||
import be.simplenotes.persistance.persistanceModule
|
|
||||||
import be.simplenotes.search.searchModule
|
|
||||||
import be.simplenotes.shared.config.DataSourceConfig
|
|
||||||
import be.simplenotes.shared.config.JwtConfig
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
import org.http4k.core.RequestContexts
|
|
||||||
import org.koin.core.context.startKoin
|
|
||||||
import org.koin.core.qualifier.named
|
|
||||||
import org.koin.core.qualifier.qualifier
|
|
||||||
import org.koin.dsl.module
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.util.*
|
|
||||||
import be.simplenotes.shared.config.ServerConfig as SimpleNotesServeConfig
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
val koin = startKoin {
|
|
||||||
modules(
|
|
||||||
persistanceModule,
|
|
||||||
configModule,
|
|
||||||
domainModule,
|
|
||||||
serverModule,
|
|
||||||
userModule,
|
|
||||||
baseModule,
|
|
||||||
noteModule,
|
|
||||||
settingsModule,
|
|
||||||
searchModule,
|
|
||||||
apiModule,
|
|
||||||
)
|
|
||||||
}.koin
|
|
||||||
|
|
||||||
val dataSourceConfig = koin.get<DataSourceConfig>()
|
|
||||||
val jwtConfig = koin.get<JwtConfig>()
|
|
||||||
val serverConfig = koin.get<SimpleNotesServeConfig>()
|
|
||||||
val logger = LoggerFactory.getLogger("SimpleNotes")
|
|
||||||
logger.info("datasource: $dataSourceConfig")
|
|
||||||
logger.info("jwt: $jwtConfig")
|
|
||||||
logger.info("server: $serverConfig")
|
|
||||||
|
|
||||||
val migrations = koin.get<DbMigrations>()
|
|
||||||
migrations.migrate()
|
|
||||||
|
|
||||||
val noteService = koin.get<NoteService>()
|
|
||||||
noteService.dropAllIndexes()
|
|
||||||
noteService.indexAll()
|
|
||||||
|
|
||||||
koin.get<Server>().start()
|
|
||||||
}
|
|
||||||
|
|
||||||
val serverModule = module {
|
|
||||||
single { Server(get(), get(), get()) }
|
|
||||||
single<StaticFileResolver> { StaticFileResolverImpl() }
|
|
||||||
single {
|
|
||||||
Router(
|
|
||||||
get(),
|
|
||||||
get(),
|
|
||||||
get(),
|
|
||||||
get(),
|
|
||||||
get(),
|
|
||||||
get(),
|
|
||||||
requiredAuth = get(AuthType.Required.qualifier),
|
|
||||||
optionalAuth = get(AuthType.Optional.qualifier),
|
|
||||||
errorFilter = get(named("ErrorFilter")),
|
|
||||||
apiAuth = get(named("apiAuthFilter")),
|
|
||||||
get()
|
|
||||||
)()
|
|
||||||
}
|
|
||||||
single { serverConfig(get()) }
|
|
||||||
single { RequestContexts() }
|
|
||||||
single(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() }
|
|
||||||
single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() }
|
|
||||||
single(named("ErrorFilter")) { ErrorFilter(get())() }
|
|
||||||
single { ErrorView(get()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val userModule = module {
|
|
||||||
single { UserController(get(), get(), get()) }
|
|
||||||
single { UserView(get()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseModule = module {
|
|
||||||
single { BaseController(get()) }
|
|
||||||
single { BaseView(get()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val noteModule = module {
|
|
||||||
single { NoteController(get(), get()) }
|
|
||||||
single { NoteView(get()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val settingsModule = module {
|
|
||||||
single { SettingsController(get(), get()) }
|
|
||||||
single { SettingView(get()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val configModule = module {
|
|
||||||
single { Config.dataSourceConfig }
|
|
||||||
single { Config.jwtConfig }
|
|
||||||
single { Config.serverConfig }
|
|
||||||
}
|
|
||||||
|
|
||||||
val apiModule = module {
|
|
||||||
single { ApiUserController(get(), get()) }
|
|
||||||
single { ApiNoteController(get(), get()) }
|
|
||||||
single {
|
|
||||||
Json {
|
|
||||||
prettyPrint = true
|
|
||||||
serializersModule = get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
single {
|
|
||||||
SerializersModule {
|
|
||||||
contextual(LocalDateTime::class, LocalDateTimeSerializer)
|
|
||||||
contextual(UUID::class, UuidSerializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
single(named("apiAuthFilter")) {
|
|
||||||
AuthFilter(
|
|
||||||
extractor = get(),
|
|
||||||
authType = AuthType.Required,
|
|
||||||
ctx = get(),
|
|
||||||
source = JwtSource.Header,
|
|
||||||
redirect = false
|
|
||||||
)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
|
||||||
override val descriptor: SerialDescriptor
|
|
||||||
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
|
||||||
encoder.encodeString(value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): LocalDateTime {
|
|
||||||
TODO("Not implemented, isn't needed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object UuidSerializer : KSerializer<UUID> {
|
|
||||||
override val descriptor: SerialDescriptor
|
|
||||||
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: UUID) {
|
|
||||||
encoder.encodeString(value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): UUID {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package be.simplenotes.app.api
|
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.json
|
|
||||||
import be.simplenotes.domain.usecases.UserService
|
|
||||||
import be.simplenotes.domain.usecases.users.login.LoginForm
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.http4k.core.Request
|
|
||||||
import org.http4k.core.Response
|
|
||||||
import org.http4k.core.Status
|
|
||||||
|
|
||||||
class ApiUserController(private val userService: UserService, private val json: Json) {
|
|
||||||
|
|
||||||
fun login(request: Request): Response {
|
|
||||||
val form = json.decodeFromString(LoginForm.serializer(), request.bodyString())
|
|
||||||
val result = userService.login(form)
|
|
||||||
return result.fold({
|
|
||||||
Response(Status.BAD_REQUEST)
|
|
||||||
}, {
|
|
||||||
Response(Status.OK).json(json.encodeToString(Token.serializer(), Token(it)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Token(val token: String)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package be.simplenotes.app.extensions
|
|
||||||
|
|
||||||
import org.http4k.core.Request
|
|
||||||
import org.http4k.core.Response
|
|
||||||
import org.http4k.core.Status.Companion.FOUND
|
|
||||||
import org.http4k.core.Status.Companion.MOVED_PERMANENTLY
|
|
||||||
|
|
||||||
fun Response.html(html: String) = body(html)
|
|
||||||
.header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
.header("Cache-Control", "no-cache")
|
|
||||||
|
|
||||||
fun Response.json(json: String) = body(json).header("Content-Type", "application/json")
|
|
||||||
|
|
||||||
fun Response.Companion.redirect(url: String, permanent: Boolean = false) =
|
|
||||||
Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url)
|
|
||||||
|
|
||||||
fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package be.simplenotes.app.filters
|
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.html
|
|
||||||
import be.simplenotes.app.views.ErrorView
|
|
||||||
import org.http4k.core.*
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.sql.SQLTransientException
|
|
||||||
|
|
||||||
class ErrorFilter(private val errorView: ErrorView) {
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
|
||||||
|
|
||||||
operator fun invoke(): Filter = Filter { next ->
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
val response = next(it)
|
|
||||||
if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND)
|
|
||||||
.html(errorView.error(ErrorView.Type.NotFound))
|
|
||||||
else response
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error(e.stackTraceToString())
|
|
||||||
if (e is SQLTransientException)
|
|
||||||
Response(Status.SERVICE_UNAVAILABLE).html(errorView.error(ErrorView.Type.SqlTransientError))
|
|
||||||
.noCache()
|
|
||||||
else
|
|
||||||
Response(Status.INTERNAL_SERVER_ERROR).html(errorView.error(ErrorView.Type.Other)).noCache()
|
|
||||||
} catch (e: NotImplementedError) {
|
|
||||||
logger.error(e.stackTraceToString())
|
|
||||||
Response(Status.NOT_IMPLEMENTED).html(errorView.error(ErrorView.Type.Other)).noCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package be.simplenotes.app.filters
|
|
||||||
|
|
||||||
import org.http4k.core.Filter
|
|
||||||
import org.http4k.core.HttpHandler
|
|
||||||
import org.http4k.core.Method
|
|
||||||
import org.http4k.core.Request
|
|
||||||
|
|
||||||
object ImmutableFilter {
|
|
||||||
operator fun invoke() = Filter { next: HttpHandler ->
|
|
||||||
{ request: Request ->
|
|
||||||
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package be.simplenotes.app.filters
|
|
||||||
|
|
||||||
import org.http4k.core.Filter
|
|
||||||
import org.http4k.core.HttpHandler
|
|
||||||
import org.http4k.core.Request
|
|
||||||
|
|
||||||
object SecurityFilter {
|
|
||||||
operator fun invoke() = Filter { next: HttpHandler ->
|
|
||||||
{ request: Request ->
|
|
||||||
val response = next(request)
|
|
||||||
.header("X-Content-Type-Options", "nosniff")
|
|
||||||
|
|
||||||
if (response.header("Content-Type")?.contains("text/html") == true) {
|
|
||||||
response
|
|
||||||
.header("Content-Security-Policy", "default-src 'self'")
|
|
||||||
.header("Referrer-Policy", "no-referrer")
|
|
||||||
} else response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package be.simplenotes.app.views
|
|
||||||
|
|
||||||
import be.simplenotes.app.utils.StaticFileResolver
|
|
||||||
import be.simplenotes.domain.security.JwtPayload
|
|
||||||
import kotlinx.html.*
|
|
||||||
import kotlinx.html.div
|
|
||||||
import org.intellij.lang.annotations.Language
|
|
||||||
|
|
||||||
class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
|
||||||
fun renderHome(jwtPayload: JwtPayload?) = renderPage(
|
|
||||||
title = "Home",
|
|
||||||
description = "A fast and simple note taking website",
|
|
||||||
jwtPayload = jwtPayload
|
|
||||||
) {
|
|
||||||
section("text-center my-2 p-2") {
|
|
||||||
h1("text-5xl casual") {
|
|
||||||
span("text-teal-300") { +"Simplenotes " }
|
|
||||||
+"- access your notes anywhere"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div("container mx-auto flex flex-wrap justify-center content-center") {
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
@Language("html")
|
|
||||||
val html =
|
|
||||||
"""
|
|
||||||
<div aria-label="demo" class="md:order-1 order-2 flipped p-4 my-10 w-full md:w-1/2">
|
|
||||||
<div class="flex justify-between mb-4">
|
|
||||||
<h1 class="text-2xl underline">Notes</h1>
|
|
||||||
<span>
|
|
||||||
<span class="btn btn-teal pointer-events-none">Trash (3)</span>
|
|
||||||
<span class="ml-2 btn btn-green pointer-events-none">New</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<form class="md:space-x-2" id="search">
|
|
||||||
<input aria-label="demo-search" name="search" disabled="" value="tag:"demo"">
|
|
||||||
<span id="buttons">
|
|
||||||
<button type="button" disabled="" class="btn btn-green pointer-events-none">search</button>
|
|
||||||
<span class="btn btn-red pointer-events-none">clear</span>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table id="notes">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="w-1/2">Title</th>
|
|
||||||
<th scope="col" class="w-1/4">Updated</th>
|
|
||||||
<th scope="col" class="w-1/4">Tags</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><span class="text-blue-200 font-semibold underline">Formula 1</span></td>
|
|
||||||
<td class="text-center">moments ago</td>
|
|
||||||
<td>
|
|
||||||
<ul class="inline flex flex-wrap justify-center">
|
|
||||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span ></li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><span class="text-blue-200 font-semibold underline">Syntax highlighting</span></td>
|
|
||||||
<td class="text-center">2 hours ago</td>
|
|
||||||
<td>
|
|
||||||
<ul class="inline flex flex-wrap justify-center">
|
|
||||||
<li class="mx-2 my-1"><span class="tag disabled">#features</span></li>
|
|
||||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><span class="text-blue-200 font-semibold underline">report</span></td>
|
|
||||||
<td class="text-center">5 days ago</td>
|
|
||||||
<td>
|
|
||||||
<ul class="inline flex flex-wrap justify-center">
|
|
||||||
<li class="mx-2 my-1"><span class="tag disabled">#study</span></li>
|
|
||||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
+html
|
|
||||||
}
|
|
||||||
|
|
||||||
welcome()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
private inline fun DIV.welcome() {
|
|
||||||
div("w-full my-auto md:w-1/2 md:order-2 order-1 text-center") {
|
|
||||||
div("m-4 rounded-lg p-6") {
|
|
||||||
p("text-teal-400") {
|
|
||||||
h2("text-3xl text-teal-400 underline") { +"Features:" }
|
|
||||||
ul("list-disc text-lg list-inside") {
|
|
||||||
li { +"Markdown support" }
|
|
||||||
li { +"Full text search" }
|
|
||||||
li { +"Structured search" }
|
|
||||||
li { +"Code highlighting" }
|
|
||||||
li { +"Fast and lightweight" }
|
|
||||||
li { +"No tracking" }
|
|
||||||
li { +"Works without javascript" }
|
|
||||||
li { +"Data export" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1 +0,0 @@
|
|||||||
package be.simplenotes.app
|
|
||||||
@@ -11,7 +11,11 @@ simplenotes.be {
|
|||||||
import strict-transport
|
import strict-transport
|
||||||
header -Server
|
header -Server
|
||||||
|
|
||||||
reverse_proxy http://localhost:8080
|
reverse_proxy http://localhost:8080 {
|
||||||
|
health_path /health
|
||||||
|
health_interval 5s
|
||||||
|
health_timeout 200ms
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dev.simplenotes.be {
|
dev.simplenotes.be {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "css",
|
"name": "css",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"css": "NODE_ENV=dev MANIFEST=../app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../app/src/main/resources/static/styles.css",
|
"css": "NODE_ENV=dev MANIFEST=../simplenotes-app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../simplenotes-app/src/main/resources/static/styles.css",
|
||||||
"css-purge": "NODE_ENV=production MANIFEST=../app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../app/src/main/resources/static/styles.css"
|
"css-purge": "NODE_ENV=production MANIFEST=../simplenotes-app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../simplenotes-app/src/main/resources/static/styles.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.8.6",
|
"autoprefixer": "^9.8.6",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
purge: {
|
purge: {
|
||||||
content: [
|
content: [
|
||||||
'../app/src/main/kotlin/views/**/*.kt'
|
'../simplenotes-app/src/main/kotlin/be/simplenotes/app/views/**/*.kt'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
rm app/src/main/resources/css-manifest.json
|
rm simplenotes-app/src/main/resources/css-manifest.json
|
||||||
rm app/src/main/resources/static/styles*
|
rm simplenotes-app/src/main/resources/static/styles*
|
||||||
|
|
||||||
yarn --cwd css run css-purge \
|
yarn --cwd css run css-purge \
|
||||||
&& docker build -t hubv/simplenotes:latest . \
|
&& docker build -t hubv/simplenotes:latest . \
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- notes-db-volume:/var/lib/mysql
|
- notes-db-volume:/var/lib/mysql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
test: "mysql --protocol=tcp -u simplenotes -p$MYSQL_PASSWORD -e 'show databases'"
|
||||||
timeout: 10s
|
interval: 5s
|
||||||
|
timeout: 1s
|
||||||
|
start_period: 2s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
simplenotes:
|
simplenotes:
|
||||||
@@ -39,6 +41,12 @@ services:
|
|||||||
# - PASSWORD
|
# - PASSWORD
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:8080:8080
|
- 127.0.0.1:8080:8080
|
||||||
|
healthcheck:
|
||||||
|
test: "curl --fail -s http://localhost:8080/health"
|
||||||
|
interval: 5s
|
||||||
|
timeout: 1s
|
||||||
|
start_period: 2s
|
||||||
|
retries: 3
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<project>
|
|
||||||
<parent>
|
|
||||||
<artifactId>parent</artifactId>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>domain</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>shared</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>shared</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<type>test-jar</type>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.konform</groupId>
|
|
||||||
<artifactId>konform-jvm</artifactId>
|
|
||||||
<version>0.2.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mindrot</groupId>
|
|
||||||
<artifactId>jbcrypt</artifactId>
|
|
||||||
<version>0.4</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.auth0</groupId>
|
|
||||||
<artifactId>java-jwt</artifactId>
|
|
||||||
<version>3.10.3</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.vladsch.flexmark</groupId>
|
|
||||||
<artifactId>flexmark</artifactId>
|
|
||||||
<version>0.62.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.vladsch.flexmark</groupId>
|
|
||||||
<artifactId>flexmark-ext-gfm-tasklist</artifactId>
|
|
||||||
<version>0.62.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.yaml</groupId>
|
|
||||||
<artifactId>snakeyaml</artifactId>
|
|
||||||
<version>1.26</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
|
|
||||||
<artifactId>owasp-java-html-sanitizer</artifactId>
|
|
||||||
<version>20200713.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
|
||||||
<artifactId>kotlinx-serialization-runtime</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package be.simplenotes.domain.usecases.export
|
|
||||||
|
|
||||||
interface ExportUseCase {
|
|
||||||
fun export(userId: Int): String
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package be.simplenotes.domain.usecases.export
|
|
||||||
|
|
||||||
import be.simplenotes.domain.model.ExportedNote
|
|
||||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
internal class ExportUseCaseImpl(private val noteRepository: NoteRepository) : ExportUseCase {
|
|
||||||
override fun export(userId: Int): String {
|
|
||||||
val module = SerializersModule {
|
|
||||||
contextual(LocalDateTime::class, LocalDateTimeSerializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
val json = Json {
|
|
||||||
prettyPrint = true
|
|
||||||
serializersModule = module
|
|
||||||
}
|
|
||||||
|
|
||||||
val notes = noteRepository.export(userId)
|
|
||||||
return json.encodeToString(ListSerializer(ExportedNote.serializer()), notes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
|
||||||
override val descriptor: SerialDescriptor
|
|
||||||
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
|
||||||
encoder.encodeString(value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): LocalDateTime {
|
|
||||||
TODO("Not implemented, isn't needed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package be.simplenotes.domain
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty file @see [root-package-declaration](https://discuss.kotlinlang.org/t/root-package-declaration-to-reduce-folder-clutter/2247/4)
|
|
||||||
*/
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package be.simplenotes.persistance
|
|
||||||
|
|
||||||
import be.simplenotes.shared.config.DataSourceConfig
|
|
||||||
import org.flywaydb.core.Flyway
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
internal class DbMigrationsImpl(
|
|
||||||
private val dataSource: DataSource,
|
|
||||||
private val dataSourceConfig: DataSourceConfig
|
|
||||||
) : DbMigrations {
|
|
||||||
override fun migrate() {
|
|
||||||
|
|
||||||
val migrationDir = when {
|
|
||||||
dataSourceConfig.jdbcUrl.contains("mariadb") -> "db/migration/mariadb"
|
|
||||||
else -> "db/migration/other"
|
|
||||||
}
|
|
||||||
|
|
||||||
Flyway.configure()
|
|
||||||
.dataSource(dataSource)
|
|
||||||
.locations(migrationDir)
|
|
||||||
.load()
|
|
||||||
.migrate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package be.simplenotes.persistance
|
|
||||||
|
|
||||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
|
||||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
|
||||||
import be.simplenotes.persistance.notes.NoteRepositoryImpl
|
|
||||||
import be.simplenotes.persistance.users.UserRepositoryImpl
|
|
||||||
import be.simplenotes.shared.config.DataSourceConfig
|
|
||||||
import com.zaxxer.hikari.HikariConfig
|
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
|
||||||
import me.liuwj.ktorm.database.*
|
|
||||||
import org.koin.dsl.module
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
interface DbMigrations {
|
|
||||||
fun migrate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
|
|
||||||
val hikariConfig = HikariConfig().also {
|
|
||||||
it.jdbcUrl = conf.jdbcUrl
|
|
||||||
it.driverClassName = conf.driverClassName
|
|
||||||
it.username = conf.username
|
|
||||||
it.password = conf.password
|
|
||||||
it.maximumPoolSize = conf.maximumPoolSize
|
|
||||||
it.connectionTimeout = conf.connectionTimeout
|
|
||||||
}
|
|
||||||
return HikariDataSource(hikariConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
val persistanceModule = module {
|
|
||||||
single<UserRepository> { UserRepositoryImpl(get()) }
|
|
||||||
single<NoteRepository> { NoteRepositoryImpl(get()) }
|
|
||||||
single<DbMigrations> { DbMigrationsImpl(get(), get()) }
|
|
||||||
single<DataSource> { hikariDataSource(get()) }
|
|
||||||
single { Database.connect(get<DataSource>()) }
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package be.simplenotes.persistance.users
|
|
||||||
|
|
||||||
import be.simplenotes.domain.model.PersistedUser
|
|
||||||
import be.simplenotes.domain.model.User
|
|
||||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
|
||||||
import me.liuwj.ktorm.database.*
|
|
||||||
import me.liuwj.ktorm.dsl.*
|
|
||||||
import me.liuwj.ktorm.entity.*
|
|
||||||
import java.sql.SQLIntegrityConstraintViolationException
|
|
||||||
|
|
||||||
internal class UserRepositoryImpl(private val db: Database) : UserRepository {
|
|
||||||
override fun create(user: User): PersistedUser? {
|
|
||||||
return try {
|
|
||||||
db.useTransaction {
|
|
||||||
val id = db.insertAndGenerateKey(Users) {
|
|
||||||
it.username to user.username
|
|
||||||
it.password to user.password
|
|
||||||
} as Int
|
|
||||||
PersistedUser(user.username, user.password, id)
|
|
||||||
}
|
|
||||||
} catch (e: SQLIntegrityConstraintViolationException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser()
|
|
||||||
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
|
|
||||||
override fun exists(username: String) = db.users.any { it.username eq username }
|
|
||||||
override fun exists(id: Int) = db.users.any { it.id eq id }
|
|
||||||
override fun delete(id: Int) = db.useTransaction { db.delete(Users) { it.id eq id } == 1 }
|
|
||||||
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package be.simplenotes.persistance
|
|
||||||
@@ -1,23 +1,27 @@
|
|||||||
<project>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>parent</artifactId>
|
<artifactId>simplenotes-parent</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>persistance</module>
|
<module>simplenotes-persistance</module>
|
||||||
<module>app</module>
|
<module>simplenotes-app</module>
|
||||||
<module>domain</module>
|
<module>simplenotes-domain</module>
|
||||||
<module>shared</module>
|
<module>simplenotes-search</module>
|
||||||
<module>search</module>
|
<module>simplenotes-types</module>
|
||||||
|
<module>simplenotes-config</module>
|
||||||
|
<module>simplenotes-test-resources</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>14</java.version>
|
<java.version>14</java.version>
|
||||||
<kotlin.version>1.4.0</kotlin.version>
|
<kotlin.version>1.4.10</kotlin.version>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.6.2</junit.version>
|
||||||
|
|
||||||
<kotlin.code.style>official</kotlin.code.style>
|
<kotlin.code.style>official</kotlin.code.style>
|
||||||
@@ -27,6 +31,8 @@
|
|||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
|
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
|
||||||
|
|
||||||
|
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -35,101 +41,83 @@
|
|||||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.koin</groupId>
|
|
||||||
<artifactId>koin-core</artifactId>
|
|
||||||
<version>2.1.6</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
<version>1.2.3</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.arrow-kt</groupId>
|
|
||||||
<artifactId>arrow-core</artifactId>
|
|
||||||
<version>0.10.5</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- region tests -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter</artifactId>
|
|
||||||
<version>${junit.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter-params</artifactId>
|
|
||||||
<version>${junit.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
|
||||||
<artifactId>kotlin-test-junit</artifactId>
|
|
||||||
<version>${kotlin.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.mockk</groupId>
|
|
||||||
<artifactId>mockk</artifactId>
|
|
||||||
<version>1.10.0</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.koin</groupId>
|
|
||||||
<artifactId>koin-test</artifactId>
|
|
||||||
<version>2.1.6</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.natpryce</groupId>
|
|
||||||
<artifactId>hamkrest</artifactId>
|
|
||||||
<version>1.7.0.3</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.assertj</groupId>
|
|
||||||
<artifactId>assertj-core</artifactId>
|
|
||||||
<version>3.16.1</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<!-- endregion -->
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||||
|
|
||||||
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.4</version>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M4</version>
|
<version>3.0.0-M5</version>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.maven.surefire</groupId>
|
<groupId>org.apache.maven.surefire</groupId>
|
||||||
<artifactId>surefire-junit-platform</artifactId>
|
<artifactId>surefire-junit-platform</artifactId>
|
||||||
<version>3.0.0-M4</version>
|
<version>3.0.0-M5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
<version>3.0.0-M3</version>
|
||||||
</plugin>
|
<executions>
|
||||||
<plugin>
|
<execution>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<id>enforce</id>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<goals>
|
||||||
<version>3.1.2</version>
|
<goal>enforce</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<banDuplicatePomDependencyVersions/>
|
||||||
|
<requireMavenVersion>
|
||||||
|
<version>3.6</version>
|
||||||
|
</requireMavenVersion>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>kotlin-maven-plugin</artifactId>
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
<executions>
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>kapt</id>
|
||||||
|
<goals>
|
||||||
|
<goal>kapt</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<annotationProcessorPath>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
</annotationProcessorPath>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>compile</id>
|
<id>compile</id>
|
||||||
<phase>process-sources</phase>
|
<phase>process-sources</phase>
|
||||||
@@ -165,39 +153,81 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
<artifactId>kotlin-bom</artifactId>
|
||||||
<version>${kotlin.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
|
||||||
<artifactId>kotlin-stdlib-jdk7</artifactId>
|
|
||||||
<version>${kotlin.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
|
||||||
<artifactId>kotlin-stdlib-common</artifactId>
|
|
||||||
<version>${kotlin.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
|
||||||
<artifactId>kotlin-stdlib</artifactId>
|
|
||||||
<version>${kotlin.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
|
||||||
<artifactId>kotlin-reflect</artifactId>
|
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
<artifactId>kotlinx-serialization-runtime</artifactId>
|
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||||
<version>1.0-M1-1.4.0-rc</version>
|
<version>1.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.koin</groupId>
|
||||||
|
<artifactId>koin-core</artifactId>
|
||||||
|
<version>2.1.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.2.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.arrow-kt</groupId>
|
||||||
|
<artifactId>arrow-core</artifactId>
|
||||||
|
<version>0.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>1.7.25</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
|
<artifactId>ktorm-core</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
|
<artifactId>ktorm-support-mysql</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- region tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.mockk</groupId>
|
||||||
|
<artifactId>mockk</artifactId>
|
||||||
|
<version>1.10.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.natpryce</groupId>
|
||||||
|
<artifactId>hamkrest</artifactId>
|
||||||
|
<version>1.7.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>3.16.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- endregion -->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package be.simplenotes.shared
|
|
||||||
@@ -1,48 +1,70 @@
|
|||||||
<project>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>parent</artifactId>
|
<artifactId>simplenotes-parent</artifactId>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>app</artifactId>
|
<artifactId>simplenotes-app</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<http4k.version>3.258.0</http4k.version>
|
<http4k.version>3.268.0</http4k.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>persistance</artifactId>
|
<artifactId>simplenotes-persistance</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>search</artifactId>
|
<artifactId>simplenotes-search</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>simplenotes-domain</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>shared</artifactId>
|
<artifactId>simplenotes-config</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.http4k</groupId>
|
<groupId>org.http4k</groupId>
|
||||||
<artifactId>http4k-core</artifactId>
|
<artifactId>http4k-core</artifactId>
|
||||||
<version>${http4k.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.http4k</groupId>
|
<groupId>org.http4k</groupId>
|
||||||
<artifactId>http4k-server-jetty</artifactId>
|
<artifactId>http4k-server-jetty</artifactId>
|
||||||
<version>${http4k.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>9.4.32.v20200930</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
<version>9.4.32.v20200930</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<version>4.0.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
<artifactId>kotlinx-html-jvm</artifactId>
|
<artifactId>kotlinx-html-jvm</artifactId>
|
||||||
@@ -50,7 +72,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlinx</groupId>
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
<artifactId>kotlinx-serialization-runtime</artifactId>
|
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ocpsoft.prettytime</groupId>
|
<groupId>org.ocpsoft.prettytime</groupId>
|
||||||
@@ -58,9 +80,24 @@
|
|||||||
<version>4.0.5.Final</version>
|
<version>4.0.5.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>shared</artifactId>
|
<artifactId>simplenotes-test-resources</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
@@ -68,17 +105,32 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.http4k</groupId>
|
<groupId>org.http4k</groupId>
|
||||||
<artifactId>http4k-testing-hamkrest</artifactId>
|
<artifactId>http4k-testing-hamkrest</artifactId>
|
||||||
<version>${http4k.version}</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
|
<artifactId>ktorm-core</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.http4k</groupId>
|
||||||
|
<artifactId>http4k-bom</artifactId>
|
||||||
|
<version>${http4k.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.2.4</version>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package be.simplenotes.app
|
package be.simplenotes.app
|
||||||
|
|
||||||
import be.simplenotes.shared.config.DataSourceConfig
|
import be.simplenotes.config.DataSourceConfig
|
||||||
import be.simplenotes.shared.config.JwtConfig
|
import be.simplenotes.config.JwtConfig
|
||||||
import be.simplenotes.shared.config.ServerConfig
|
import be.simplenotes.config.ServerConfig
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object Config {
|
class Config {
|
||||||
//region Config loading
|
//region Config loading
|
||||||
private val properties: Properties = javaClass
|
private val properties: Properties = javaClass
|
||||||
.getResource("/application.properties")
|
.getResource("/application.properties")
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package be.simplenotes.app
|
||||||
|
|
||||||
|
import org.http4k.server.Http4kServer
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
||||||
|
|
||||||
|
class Server(
|
||||||
|
private val config: SimpleNotesServerConfig,
|
||||||
|
private val http4kServer: Http4kServer,
|
||||||
|
) {
|
||||||
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
fun start(): Server {
|
||||||
|
http4kServer.start()
|
||||||
|
logger.info("Listening on http://${config.host}:${config.port}")
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
logger.info("Stopping server")
|
||||||
|
http4kServer.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package be.simplenotes.app
|
||||||
|
|
||||||
|
import be.simplenotes.app.extensions.addShutdownHook
|
||||||
|
import be.simplenotes.app.modules.*
|
||||||
|
import be.simplenotes.domain.domainModule
|
||||||
|
import be.simplenotes.persistance.migrationModule
|
||||||
|
import be.simplenotes.persistance.persistanceModule
|
||||||
|
import be.simplenotes.search.searchModule
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
|
import org.koin.core.context.unloadKoinModules
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
startKoin {
|
||||||
|
modules(
|
||||||
|
serverModule,
|
||||||
|
persistanceModule,
|
||||||
|
migrationModule,
|
||||||
|
configModule,
|
||||||
|
baseModule,
|
||||||
|
userModule,
|
||||||
|
noteModule,
|
||||||
|
settingsModule,
|
||||||
|
domainModule,
|
||||||
|
searchModule,
|
||||||
|
apiModule,
|
||||||
|
jsonModule
|
||||||
|
)
|
||||||
|
}.addShutdownHook()
|
||||||
|
|
||||||
|
unloadKoinModules(listOf(migrationModule, configModule))
|
||||||
|
}
|
||||||
@@ -1,55 +1,46 @@
|
|||||||
package be.simplenotes.app.api
|
package be.simplenotes.app.api
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.json
|
import be.simplenotes.app.extensions.auto
|
||||||
import be.simplenotes.app.utils.parseSearchTerms
|
import be.simplenotes.app.utils.parseSearchTerms
|
||||||
import be.simplenotes.domain.model.PersistedNote
|
import be.simplenotes.types.PersistedNote
|
||||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import be.simplenotes.domain.security.JwtPayload
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.usecases.NoteService
|
import be.simplenotes.domain.usecases.NoteService
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.http4k.core.Request
|
import org.http4k.core.Request
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
import org.http4k.core.Status.Companion.BAD_REQUEST
|
import org.http4k.core.Status.Companion.BAD_REQUEST
|
||||||
import org.http4k.core.Status.Companion.NOT_FOUND
|
import org.http4k.core.Status.Companion.NOT_FOUND
|
||||||
import org.http4k.core.Status.Companion.OK
|
import org.http4k.core.Status.Companion.OK
|
||||||
import org.http4k.routing.path
|
import org.http4k.lens.Path
|
||||||
|
import org.http4k.lens.uuid
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ApiNoteController(private val noteService: NoteService, private val json: Json) {
|
class ApiNoteController(private val noteService: NoteService, private val json: Json) {
|
||||||
|
|
||||||
fun createNote(request: Request, jwtPayload: JwtPayload): Response {
|
fun createNote(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
|
val content = noteContentLens(request)
|
||||||
return noteService.create(jwtPayload.userId, content).fold(
|
return noteService.create(jwtPayload.userId, content).fold(
|
||||||
{
|
{ Response(BAD_REQUEST) },
|
||||||
Response(BAD_REQUEST)
|
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
|
||||||
},
|
|
||||||
{
|
|
||||||
Response(OK).json(json.encodeToString(UuidContent.serializer(), UuidContent(it.uuid)))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notes(request: Request, jwtPayload: JwtPayload): Response {
|
fun notes(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val notes = noteService.paginatedNotes(jwtPayload.userId, page = 1).notes
|
val notes = noteService.paginatedNotes(jwtPayload.userId, page = 1).notes
|
||||||
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
return persistedNotesMetadataLens(notes, Response(OK))
|
||||||
return Response(OK).json(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun note(request: Request, jwtPayload: JwtPayload): Response {
|
fun note(request: Request, jwtPayload: JwtPayload): Response =
|
||||||
val uuid = request.path("uuid")!!
|
noteService.find(jwtPayload.userId, uuidLens(request))
|
||||||
|
?.let { persistedNoteLens(it, Response(OK)) }
|
||||||
return noteService.find(jwtPayload.userId, UUID.fromString(uuid))
|
|
||||||
?.let { Response(OK).json(json.encodeToString(PersistedNote.serializer(), it)) }
|
|
||||||
?: Response(NOT_FOUND)
|
?: Response(NOT_FOUND)
|
||||||
}
|
|
||||||
|
|
||||||
fun update(request: Request, jwtPayload: JwtPayload): Response {
|
fun update(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val uuid = UUID.fromString(request.path("uuid")!!)
|
val content = noteContentLens(request)
|
||||||
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
|
return noteService.update(jwtPayload.userId, uuidLens(request), content).fold({
|
||||||
return noteService.update(jwtPayload.userId, uuid, content).fold({
|
|
||||||
Response(BAD_REQUEST)
|
Response(BAD_REQUEST)
|
||||||
}, {
|
}, {
|
||||||
if (it == null) Response(NOT_FOUND)
|
if (it == null) Response(NOT_FOUND)
|
||||||
@@ -58,13 +49,19 @@ class ApiNoteController(private val noteService: NoteService, private val json:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val query = json.decodeFromString(SearchContent.serializer(), request.bodyString()).query
|
val query = searchContentLens(request)
|
||||||
val terms = parseSearchTerms(query)
|
val terms = parseSearchTerms(query)
|
||||||
val notes = noteService.search(jwtPayload.userId, terms)
|
val notes = noteService.search(jwtPayload.userId, terms)
|
||||||
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
return persistedNotesMetadataLens(notes, Response(OK))
|
||||||
return Response(OK).json(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val uuidContentLens = json.auto<UuidContent>().toLens()
|
||||||
|
private val noteContentLens = json.auto<NoteContent>().map { it.content }.toLens()
|
||||||
|
private val searchContentLens = json.auto<SearchContent>().map { it.query }.toLens()
|
||||||
|
private val persistedNotesMetadataLens = json.auto<List<PersistedNoteMetadata>>().toLens()
|
||||||
|
private val persistedNoteLens = json.auto<PersistedNote>().toLens()
|
||||||
|
private val uuidLens = Path.uuid().of("uuid")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package be.simplenotes.app.api
|
||||||
|
|
||||||
|
import be.simplenotes.app.extensions.auto
|
||||||
|
import be.simplenotes.domain.usecases.UserService
|
||||||
|
import be.simplenotes.domain.usecases.users.login.LoginForm
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
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
|
||||||
|
|
||||||
|
class ApiUserController(private val userService: UserService, private val json: Json) {
|
||||||
|
private val tokenLens = json.auto<Token>().toLens()
|
||||||
|
private val loginFormLens = json.auto<LoginForm>().toLens()
|
||||||
|
|
||||||
|
fun login(request: Request) = userService
|
||||||
|
.login(loginFormLens(request))
|
||||||
|
.fold(
|
||||||
|
{ Response(BAD_REQUEST) },
|
||||||
|
{ tokenLens(Token(it), Response(OK)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Token(val token: String)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package be.simplenotes.app.controllers
|
||||||
|
|
||||||
|
import be.simplenotes.persistance.DbHealthCheck
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status.Companion.OK
|
||||||
|
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
|
||||||
|
|
||||||
|
class HealthCheckController(private val dbHealthCheck: DbHealthCheck) {
|
||||||
|
fun healthCheck(request: Request) =
|
||||||
|
if (dbHealthCheck.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
|
||||||
|
}
|
||||||
@@ -7,10 +7,7 @@ import be.simplenotes.domain.security.JwtPayload
|
|||||||
import be.simplenotes.domain.usecases.UserService
|
import be.simplenotes.domain.usecases.UserService
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
||||||
import org.http4k.core.Method
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Request
|
|
||||||
import org.http4k.core.Response
|
|
||||||
import org.http4k.core.Status
|
|
||||||
import org.http4k.core.body.form
|
import org.http4k.core.body.form
|
||||||
import org.http4k.core.cookie.invalidateCookie
|
import org.http4k.core.cookie.invalidateCookie
|
||||||
|
|
||||||
@@ -49,18 +46,29 @@ class SettingsController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun export(request: Request, jwtPayload: JwtPayload): Response {
|
private fun attachment(filename: String, contentType: String) = { response: Response ->
|
||||||
val isDownload = request.form("download") != null
|
val name = filename.replace("[^a-zA-Z0-9-_.]".toRegex(), "_")
|
||||||
val json = userService.export(jwtPayload.userId)
|
response
|
||||||
val res = Response(Status.OK).body(json).header("Content-Type", "application/json")
|
.header("Content-Disposition", "attachment; filename=\"$name\"")
|
||||||
return if (isDownload) res.header(
|
.header("Content-Type", contentType)
|
||||||
"Content-Disposition",
|
|
||||||
"attachment; filename=\"simplenotes-export-${sanitizeFilename(jwtPayload.username)}.json\""
|
|
||||||
)
|
|
||||||
else res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sanitizeFilename(inputName: String): String = inputName.replace("[^a-zA-Z0-9-_.]".toRegex(), "_")
|
fun export(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val isDownload = request.form("download") != null
|
||||||
|
|
||||||
|
return if (isDownload) {
|
||||||
|
val filename = "simplenotes-export-${jwtPayload.username}"
|
||||||
|
if (request.form("format") == "zip") {
|
||||||
|
val zip = userService.exportAsZip(jwtPayload.userId)
|
||||||
|
Response(Status.OK)
|
||||||
|
.with(attachment("$filename.zip", "application/zip"))
|
||||||
|
.body(zip)
|
||||||
|
} else
|
||||||
|
Response(Status.OK)
|
||||||
|
.with(attachment("$filename.json", "application/json"))
|
||||||
|
.body(userService.exportAsJson(jwtPayload.userId))
|
||||||
|
} else Response(Status.OK).body(userService.exportAsJson(jwtPayload.userId)).header("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
private fun Request.deleteForm(jwtPayload: JwtPayload) =
|
private fun Request.deleteForm(jwtPayload: JwtPayload) =
|
||||||
DeleteForm(jwtPayload.username, form("password"), form("checked") != null)
|
DeleteForm(jwtPayload.username, form("password"), form("checked") != null)
|
||||||
@@ -10,7 +10,7 @@ import be.simplenotes.domain.usecases.users.login.*
|
|||||||
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
|
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
|
||||||
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
||||||
import be.simplenotes.domain.usecases.users.register.UserExists
|
import be.simplenotes.domain.usecases.users.register.UserExists
|
||||||
import be.simplenotes.shared.config.JwtConfig
|
import be.simplenotes.config.JwtConfig
|
||||||
import org.http4k.core.Method.GET
|
import org.http4k.core.Method.GET
|
||||||
import org.http4k.core.Request
|
import org.http4k.core.Request
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package be.simplenotes.app.extensions
|
||||||
|
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.http4k.asString
|
||||||
|
import org.http4k.core.Body
|
||||||
|
import org.http4k.core.ContentType
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status.Companion.FOUND
|
||||||
|
import org.http4k.core.Status.Companion.MOVED_PERMANENTLY
|
||||||
|
import org.http4k.lens.*
|
||||||
|
|
||||||
|
fun Response.html(html: String) = body(html)
|
||||||
|
.header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
fun Response.Companion.redirect(url: String, permanent: Boolean = false) =
|
||||||
|
Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url)
|
||||||
|
|
||||||
|
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
|
||||||
|
).map(
|
||||||
|
{ it.payload.asString() },
|
||||||
|
{ Body(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified T> Json.auto(): BiDiBodyLensSpec<T> = bodyLens.map(
|
||||||
|
{ decodeFromString(it) },
|
||||||
|
{ encodeToString(it) }
|
||||||
|
)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package be.simplenotes.app.extensions
|
||||||
|
|
||||||
|
import org.koin.core.KoinApplication
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
fun KoinApplication.addShutdownHook() {
|
||||||
|
Runtime.getRuntime().addShutdownHook(
|
||||||
|
thread(start = false) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -19,9 +19,8 @@ class AuthFilter(
|
|||||||
private val ctx: RequestContexts,
|
private val ctx: RequestContexts,
|
||||||
private val source: JwtSource = JwtSource.Cookie,
|
private val source: JwtSource = JwtSource.Cookie,
|
||||||
private val redirect: Boolean = true,
|
private val redirect: Boolean = true,
|
||||||
) {
|
) : Filter {
|
||||||
operator fun invoke() = Filter { next ->
|
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||||
{
|
|
||||||
val token = when (source) {
|
val token = when (source) {
|
||||||
JwtSource.Header -> it.bearerTokenHeader()
|
JwtSource.Header -> it.bearerTokenHeader()
|
||||||
JwtSource.Cookie -> it.bearerTokenCookie()
|
JwtSource.Cookie -> it.bearerTokenCookie()
|
||||||
@@ -39,7 +38,6 @@ class AuthFilter(
|
|||||||
else -> next(it)
|
else -> next(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
|
import be.simplenotes.app.extensions.html
|
||||||
|
import be.simplenotes.app.views.ErrorView
|
||||||
|
import be.simplenotes.app.views.ErrorView.Type.*
|
||||||
|
import org.http4k.core.*
|
||||||
|
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
|
||||||
|
import org.http4k.core.Status.Companion.NOT_FOUND
|
||||||
|
import org.http4k.core.Status.Companion.NOT_IMPLEMENTED
|
||||||
|
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.sql.SQLTransientException
|
||||||
|
|
||||||
|
class ErrorFilter(private val errorView: ErrorView) : Filter {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
private fun errorResponse(status: Status): Response {
|
||||||
|
val type = when (status) {
|
||||||
|
SERVICE_UNAVAILABLE -> SqlTransientError
|
||||||
|
NOT_FOUND -> NotFound
|
||||||
|
NOT_IMPLEMENTED -> Other
|
||||||
|
else -> Other
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(status).html(errorView.error(type)).noCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke(next: HttpHandler): HttpHandler = { request ->
|
||||||
|
try {
|
||||||
|
val response = next(request)
|
||||||
|
if (response.status == NOT_FOUND) errorResponse(NOT_FOUND)
|
||||||
|
else response
|
||||||
|
} catch (e: SQLTransientException) {
|
||||||
|
logger.error(e.stackTraceToString())
|
||||||
|
errorResponse(SERVICE_UNAVAILABLE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e.stackTraceToString())
|
||||||
|
errorResponse(INTERNAL_SERVER_ERROR)
|
||||||
|
} catch (e: NotImplementedError) {
|
||||||
|
logger.error(e.stackTraceToString())
|
||||||
|
errorResponse(NOT_IMPLEMENTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.http4k.core.HttpHandler
|
||||||
|
import org.http4k.core.Request
|
||||||
|
|
||||||
|
object ImmutableFilter : Filter {
|
||||||
|
override fun invoke(next: HttpHandler) = { request: Request ->
|
||||||
|
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.http4k.core.HttpHandler
|
||||||
|
import org.http4k.core.Request
|
||||||
|
|
||||||
|
object SecurityFilter : Filter {
|
||||||
|
override fun invoke(next: HttpHandler): HttpHandler = { request: Request ->
|
||||||
|
val response = next(request)
|
||||||
|
.header("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
if (response.header("Content-Type")?.contains("text/html") == true) {
|
||||||
|
response
|
||||||
|
.header("Content-Security-Policy", "default-src 'self'")
|
||||||
|
.header("Referrer-Policy", "no-referrer")
|
||||||
|
} else response
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
|
import me.liuwj.ktorm.database.Database
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.http4k.core.HttpHandler
|
||||||
|
|
||||||
|
class TransactionFilter(private val db: Database) : Filter {
|
||||||
|
override fun invoke(next: HttpHandler): HttpHandler = { request ->
|
||||||
|
db.useTransaction {
|
||||||
|
next(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package be.simplenotes.app.jetty
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server
|
||||||
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler.SESSIONS
|
||||||
|
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
|
||||||
|
|
||||||
|
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)) }
|
||||||
|
})
|
||||||
|
|
||||||
|
override fun toServer(httpHandler: HttpHandler): Http4kServer {
|
||||||
|
server.insertHandler(httpHandler.toJettyHandler())
|
||||||
|
|
||||||
|
return object : Http4kServer {
|
||||||
|
override fun start(): Http4kServer = apply {
|
||||||
|
server.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop(): Http4kServer = apply { server.stop() }
|
||||||
|
|
||||||
|
override fun port(): Int = if (port > 0) port else server.uri.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HttpHandler.toJettyHandler() = ServletContextHandler(SESSIONS).apply {
|
||||||
|
addServlet(ServletHolder(this@toJettyHandler.asServlet()), "/*")
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias ConnectorBuilder = (Server) -> ServerConnector
|
||||||
|
|
||||||
|
fun http(httpPort: Int): ConnectorBuilder = { server: Server -> ServerConnector(server).apply { port = httpPort } }
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.api.ApiNoteController
|
||||||
|
import be.simplenotes.app.api.ApiUserController
|
||||||
|
import be.simplenotes.app.filters.AuthFilter
|
||||||
|
import be.simplenotes.app.filters.AuthType
|
||||||
|
import be.simplenotes.app.filters.JwtSource
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val apiModule = module {
|
||||||
|
single { ApiUserController(get(), get()) }
|
||||||
|
single { ApiNoteController(get(), get()) }
|
||||||
|
single<Filter>(named("apiAuthFilter")) {
|
||||||
|
AuthFilter(
|
||||||
|
extractor = get(),
|
||||||
|
authType = AuthType.Required,
|
||||||
|
ctx = get(),
|
||||||
|
source = JwtSource.Header,
|
||||||
|
redirect = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.Config
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val configModule = module {
|
||||||
|
single { Config() }
|
||||||
|
single { get<Config>().dataSourceConfig }
|
||||||
|
single { get<Config>().jwtConfig }
|
||||||
|
single { get<Config>().serverConfig }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.controllers.*
|
||||||
|
import be.simplenotes.app.views.BaseView
|
||||||
|
import be.simplenotes.app.views.NoteView
|
||||||
|
import be.simplenotes.app.views.SettingView
|
||||||
|
import be.simplenotes.app.views.UserView
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val userModule = module {
|
||||||
|
single { UserController(get(), get(), get()) }
|
||||||
|
single { UserView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseModule = module {
|
||||||
|
single { HealthCheckController(get()) }
|
||||||
|
single { BaseController(get()) }
|
||||||
|
single { BaseView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val noteModule = module {
|
||||||
|
single { NoteController(get(), get()) }
|
||||||
|
single { NoteView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val settingsModule = module {
|
||||||
|
single { SettingsController(get(), get()) }
|
||||||
|
single { SettingView(get()) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.serialization.LocalDateTimeSerializer
|
||||||
|
import be.simplenotes.app.serialization.UuidSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val jsonModule = module {
|
||||||
|
single {
|
||||||
|
Json {
|
||||||
|
prettyPrint = true
|
||||||
|
serializersModule = get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
single {
|
||||||
|
SerializersModule {
|
||||||
|
contextual(LocalDateTime::class, LocalDateTimeSerializer())
|
||||||
|
contextual(UUID::class, UuidSerializer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.Server
|
||||||
|
import be.simplenotes.app.filters.AuthFilter
|
||||||
|
import be.simplenotes.app.filters.AuthType
|
||||||
|
import be.simplenotes.app.filters.ErrorFilter
|
||||||
|
import be.simplenotes.app.filters.TransactionFilter
|
||||||
|
import be.simplenotes.app.jetty.ConnectorBuilder
|
||||||
|
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.app.views.ErrorView
|
||||||
|
import be.simplenotes.config.ServerConfig
|
||||||
|
import org.eclipse.jetty.server.ServerConnector
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.http4k.core.RequestContexts
|
||||||
|
import org.http4k.routing.RoutingHttpHandler
|
||||||
|
import org.http4k.server.asServer
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.core.qualifier.qualifier
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.dsl.onClose
|
||||||
|
import org.http4k.server.ServerConfig as Http4kServerConfig
|
||||||
|
|
||||||
|
val serverModule = module {
|
||||||
|
single(createdAtStart = true) { Server(get(), get()).start() } onClose { it?.stop() }
|
||||||
|
single { get<RoutingHttpHandler>().asServer(get()) }
|
||||||
|
single<Http4kServerConfig> {
|
||||||
|
val config = get<ServerConfig>()
|
||||||
|
val builder: ConnectorBuilder = { server: org.eclipse.jetty.server.Server ->
|
||||||
|
ServerConnector(server).apply {
|
||||||
|
port = config.port
|
||||||
|
host = config.host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Jetty(config.port, builder)
|
||||||
|
}
|
||||||
|
single<StaticFileResolver> { StaticFileResolverImpl(get()) }
|
||||||
|
single {
|
||||||
|
Router(
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
requiredAuth = get(AuthType.Required.qualifier),
|
||||||
|
optionalAuth = get(AuthType.Optional.qualifier),
|
||||||
|
apiAuth = get(named("apiAuthFilter")),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
get(),
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
single { RequestContexts() }
|
||||||
|
single<Filter>(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get()) }
|
||||||
|
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
|
||||||
|
single { ErrorFilter(get()) }
|
||||||
|
single { TransactionFilter(get()) }
|
||||||
|
single { ErrorView(get()) }
|
||||||
|
}
|
||||||
@@ -2,19 +2,15 @@ package be.simplenotes.app.routes
|
|||||||
|
|
||||||
import be.simplenotes.app.api.ApiNoteController
|
import be.simplenotes.app.api.ApiNoteController
|
||||||
import be.simplenotes.app.api.ApiUserController
|
import be.simplenotes.app.api.ApiUserController
|
||||||
import be.simplenotes.app.controllers.BaseController
|
import be.simplenotes.app.controllers.*
|
||||||
import be.simplenotes.app.controllers.NoteController
|
import be.simplenotes.app.filters.*
|
||||||
import be.simplenotes.app.controllers.SettingsController
|
|
||||||
import be.simplenotes.app.controllers.UserController
|
|
||||||
import be.simplenotes.app.filters.ImmutableFilter
|
|
||||||
import be.simplenotes.app.filters.SecurityFilter
|
|
||||||
import be.simplenotes.app.filters.jwtPayload
|
|
||||||
import be.simplenotes.domain.security.JwtPayload
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import org.http4k.core.*
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Method.*
|
import org.http4k.core.Method.*
|
||||||
import org.http4k.filter.ResponseFilters
|
import org.http4k.filter.ResponseFilters.GZip
|
||||||
import org.http4k.filter.ServerFilters.InitialiseRequestContext
|
import org.http4k.filter.ServerFilters.InitialiseRequestContext
|
||||||
import org.http4k.routing.*
|
import org.http4k.routing.*
|
||||||
|
import org.http4k.routing.ResourceLoader.Companion.Classpath
|
||||||
|
|
||||||
class Router(
|
class Router(
|
||||||
private val baseController: BaseController,
|
private val baseController: BaseController,
|
||||||
@@ -23,26 +19,26 @@ class Router(
|
|||||||
private val settingsController: SettingsController,
|
private val settingsController: SettingsController,
|
||||||
private val apiUserController: ApiUserController,
|
private val apiUserController: ApiUserController,
|
||||||
private val apiNoteController: ApiNoteController,
|
private val apiNoteController: ApiNoteController,
|
||||||
|
private val healthCheckController: HealthCheckController,
|
||||||
private val requiredAuth: Filter,
|
private val requiredAuth: Filter,
|
||||||
private val optionalAuth: Filter,
|
private val optionalAuth: Filter,
|
||||||
private val errorFilter: Filter,
|
|
||||||
private val apiAuth: Filter,
|
private val apiAuth: Filter,
|
||||||
|
private val errorFilter: ErrorFilter,
|
||||||
|
private val transactionFilter: TransactionFilter,
|
||||||
private val contexts: RequestContexts,
|
private val contexts: RequestContexts,
|
||||||
) {
|
) {
|
||||||
operator fun invoke(): RoutingHttpHandler {
|
operator fun invoke(): RoutingHttpHandler {
|
||||||
|
|
||||||
val resourceLoader = ResourceLoader.Classpath(("/static"))
|
val basicRoutes =
|
||||||
val basicRoutes = routes(
|
routes(
|
||||||
ImmutableFilter().then(static(resourceLoader, "woff2" to ContentType("font/woff2"))),
|
"/health" bind GET to healthCheckController::healthCheck,
|
||||||
|
ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2")))
|
||||||
)
|
)
|
||||||
|
|
||||||
infix fun PathMethod.public(handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) }
|
val publicRoutes = routes(
|
||||||
infix fun PathMethod.protected(handler: ProtectedHandler) = this to { handler(it, it.jwtPayload(contexts)!!) }
|
|
||||||
|
|
||||||
val publicRoutes: RoutingHttpHandler = routes(
|
|
||||||
"/" bind GET public baseController::index,
|
"/" bind GET public baseController::index,
|
||||||
"/register" bind GET public userController::register,
|
"/register" bind GET public userController::register,
|
||||||
"/register" bind POST public userController::register,
|
"/register" bind POST `public transactional` userController::register,
|
||||||
"/login" bind GET public userController::login,
|
"/login" bind GET public userController::login,
|
||||||
"/login" bind POST public userController::login,
|
"/login" bind POST public userController::login,
|
||||||
"/logout" bind POST to userController::logout,
|
"/logout" bind POST to userController::logout,
|
||||||
@@ -51,18 +47,18 @@ class Router(
|
|||||||
|
|
||||||
val protectedRoutes = routes(
|
val protectedRoutes = routes(
|
||||||
"/settings" bind GET protected settingsController::settings,
|
"/settings" bind GET protected settingsController::settings,
|
||||||
"/settings" bind POST protected settingsController::settings,
|
"/settings" bind POST transactional settingsController::settings,
|
||||||
"/export" bind POST protected settingsController::export,
|
"/export" bind POST protected settingsController::export,
|
||||||
"/notes" bind GET protected noteController::list,
|
"/notes" bind GET protected noteController::list,
|
||||||
"/notes" bind POST protected noteController::search,
|
"/notes" bind POST protected noteController::search,
|
||||||
"/notes/new" bind GET protected noteController::new,
|
"/notes/new" bind GET protected noteController::new,
|
||||||
"/notes/new" bind POST protected noteController::new,
|
"/notes/new" bind POST transactional noteController::new,
|
||||||
"/notes/trash" bind GET protected noteController::trash,
|
"/notes/trash" bind GET protected noteController::trash,
|
||||||
"/notes/{uuid}" bind GET protected noteController::note,
|
"/notes/{uuid}" bind GET protected noteController::note,
|
||||||
"/notes/{uuid}" bind POST protected noteController::note,
|
"/notes/{uuid}" bind POST transactional noteController::note,
|
||||||
"/notes/{uuid}/edit" bind GET protected noteController::edit,
|
"/notes/{uuid}/edit" bind GET protected noteController::edit,
|
||||||
"/notes/{uuid}/edit" bind POST protected noteController::edit,
|
"/notes/{uuid}/edit" bind POST transactional noteController::edit,
|
||||||
"/notes/deleted/{uuid}" bind POST protected noteController::deleted,
|
"/notes/deleted/{uuid}" bind POST transactional noteController::deleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
val apiRoutes = routes(
|
val apiRoutes = routes(
|
||||||
@@ -71,10 +67,10 @@ class Router(
|
|||||||
|
|
||||||
val protectedApiRoutes = routes(
|
val protectedApiRoutes = routes(
|
||||||
"/api/notes" bind GET protected apiNoteController::notes,
|
"/api/notes" bind GET protected apiNoteController::notes,
|
||||||
"/api/notes" bind POST protected apiNoteController::createNote,
|
"/api/notes" bind POST transactional apiNoteController::createNote,
|
||||||
"/api/notes/search" bind POST protected apiNoteController::search,
|
"/api/notes/search" bind POST transactional apiNoteController::search,
|
||||||
"/api/notes/{uuid}" bind GET protected apiNoteController::note,
|
"/api/notes/{uuid}" bind GET protected apiNoteController::note,
|
||||||
"/api/notes/{uuid}" bind PUT protected apiNoteController::update,
|
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update,
|
||||||
)
|
)
|
||||||
|
|
||||||
val routes = routes(
|
val routes = routes(
|
||||||
@@ -87,11 +83,23 @@ class Router(
|
|||||||
|
|
||||||
val globalFilters = errorFilter
|
val globalFilters = errorFilter
|
||||||
.then(InitialiseRequestContext(contexts))
|
.then(InitialiseRequestContext(contexts))
|
||||||
.then(SecurityFilter())
|
.then(SecurityFilter)
|
||||||
.then(ResponseFilters.GZip())
|
.then(GZip())
|
||||||
|
|
||||||
return globalFilters.then(routes)
|
return globalFilters.then(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline infix fun PathMethod.public(crossinline handler: PublicHandler) =
|
||||||
|
this to { handler(it, it.jwtPayload(contexts)) }
|
||||||
|
|
||||||
|
private inline infix fun PathMethod.protected(crossinline handler: ProtectedHandler) =
|
||||||
|
this to { handler(it, it.jwtPayload(contexts)!!) }
|
||||||
|
|
||||||
|
private inline infix fun PathMethod.transactional(crossinline handler: ProtectedHandler) =
|
||||||
|
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)!!) }
|
||||||
|
|
||||||
|
private inline infix fun PathMethod.`public transactional`(crossinline handler: PublicHandler) =
|
||||||
|
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private typealias PublicHandler = (Request, JwtPayload?) -> Response
|
private typealias PublicHandler = (Request, JwtPayload?) -> Response
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package be.simplenotes.app.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
internal class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): LocalDateTime {
|
||||||
|
TODO("Not implemented, isn't needed")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package be.simplenotes.app.serialization
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal class UuidSerializer : KSerializer<UUID> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: UUID) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): UUID {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.app.utils
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
import be.simplenotes.domain.usecases.search.SearchTerms
|
import be.simplenotes.search.SearchTerms
|
||||||
|
|
||||||
private fun innerRegex(name: String) =
|
private fun innerRegex(name: String) =
|
||||||
"""$name:['"](.*?)['"]""".toRegex()
|
"""$name:['"](.*?)['"]""".toRegex()
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package be.simplenotes.app.utils
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
interface StaticFileResolver {
|
interface StaticFileResolver {
|
||||||
fun resolve(name: String): String?
|
fun resolve(name: String): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
class StaticFileResolverImpl : StaticFileResolver {
|
class StaticFileResolverImpl(json: Json) : StaticFileResolver {
|
||||||
private val mappings: Map<String, String>
|
private val mappings: Map<String, String>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val json = Json {}
|
|
||||||
val manifest = javaClass.getResource("/css-manifest.json").readText()
|
val manifest = javaClass.getResource("/css-manifest.json").readText()
|
||||||
val manifestObject = json.parseToJsonElement(manifest).jsonObject
|
val manifestObject = json.parseToJsonElement(manifest).jsonObject
|
||||||
val keys = manifestObject.keys
|
val keys = manifestObject.keys
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
|
import kotlinx.html.*
|
||||||
|
import kotlinx.html.ThScope.col
|
||||||
|
|
||||||
|
class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
|
fun renderHome(jwtPayload: JwtPayload?) = renderPage(
|
||||||
|
title = "Home",
|
||||||
|
description = "A fast and simple note taking website",
|
||||||
|
jwtPayload = jwtPayload
|
||||||
|
) {
|
||||||
|
section("text-center my-2 p-2") {
|
||||||
|
h1("text-5xl casual") {
|
||||||
|
span("text-teal-300") { +"SimpleNotes " }
|
||||||
|
+"- access your notes anywhere"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
h1("text-2xl underline") { +"Notes" }
|
||||||
|
span {
|
||||||
|
span("btn btn-teal pointer-events-none") { +"Trash (3)" }
|
||||||
|
span("ml-2 btn btn-green pointer-events-none") { +"New" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form(classes = "md:space-x-2") {
|
||||||
|
id = "search"
|
||||||
|
input {
|
||||||
|
attributes["aria-label"] = "demo-search"
|
||||||
|
attributes["name"] = "search"
|
||||||
|
attributes["disabled"] = ""
|
||||||
|
attributes["value"] = "tag:\"demo\""
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
id = "buttons"
|
||||||
|
button(type = ButtonType.button, classes = "btn btn-green pointer-events-none") {
|
||||||
|
attributes["disabled"] = ""
|
||||||
|
+"search"
|
||||||
|
}
|
||||||
|
span("btn btn-red pointer-events-none") { +"clear" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div("overflow-x-auto") {
|
||||||
|
demoTable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
welcome()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun DIV.demoTable() {
|
||||||
|
table {
|
||||||
|
id = "notes"
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
th(scope = col, classes = "w-1/2") { +"Title" }
|
||||||
|
th(scope = col, classes = "w-1/4") { +"Updated" }
|
||||||
|
th(scope = col, classes = "w-1/4") { +"Tags" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
listOf(
|
||||||
|
Triple("Formula 1", "moments ago", arrayOf("#demo")),
|
||||||
|
Triple("Syntax highlighting", "2 hours ago", arrayOf("#features", "#demo")),
|
||||||
|
Triple("report", "5 days ago", arrayOf("#study", "#demo")),
|
||||||
|
).forEach { (title, ago, tags) ->
|
||||||
|
tr {
|
||||||
|
td { span("text-blue-200 font-semibold underline") { +title } }
|
||||||
|
td("text-center") { +ago }
|
||||||
|
td {
|
||||||
|
ul("inline flex flex-wrap justify-center") {
|
||||||
|
tags.forEach { tag ->
|
||||||
|
li("mx-2 my-1") { span("tag disabled") { +tag } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun DIV.welcome() {
|
||||||
|
div("w-full my-auto md:w-1/2 md:order-2 order-1 text-center") {
|
||||||
|
div("m-4 rounded-lg p-6") {
|
||||||
|
h2("text-3xl text-teal-400 underline") { +"Features:" }
|
||||||
|
ul("list-disc text-lg list-inside") {
|
||||||
|
li { +"Markdown support" }
|
||||||
|
li { +"Full text search" }
|
||||||
|
li { +"Structured search" }
|
||||||
|
li { +"Code highlighting" }
|
||||||
|
li { +"Fast and lightweight" }
|
||||||
|
li { +"No tracking" }
|
||||||
|
li { +"Works without javascript" }
|
||||||
|
li { +"Data export" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ package be.simplenotes.app.views
|
|||||||
|
|
||||||
import be.simplenotes.app.utils.StaticFileResolver
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.app.views.components.*
|
import be.simplenotes.app.views.components.*
|
||||||
import be.simplenotes.domain.model.PersistedNote
|
import be.simplenotes.types.PersistedNote
|
||||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import be.simplenotes.domain.security.JwtPayload
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import io.konform.validation.ValidationError
|
import io.konform.validation.ValidationError
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
@@ -143,7 +143,6 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
|||||||
}
|
}
|
||||||
if (!shared) {
|
if (!shared) {
|
||||||
noteActionForm(note)
|
noteActionForm(note)
|
||||||
publicPrivateForm(note)
|
|
||||||
|
|
||||||
if (note.public) {
|
if (note.public) {
|
||||||
p("my-4") {
|
p("my-4") {
|
||||||
@@ -166,12 +165,29 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun DIV.noteActionForm(note: PersistedNote) {
|
private fun DIV.noteActionForm(note: PersistedNote) {
|
||||||
span("flex space-x-2 justify-end mb-4") {
|
form(method = FormMethod.post, classes = "inline flex space-x-2 justify-end mb-4") {
|
||||||
a(
|
a(
|
||||||
href = "/notes/${note.uuid}/edit",
|
href = "/notes/${note.uuid}/edit",
|
||||||
classes = "btn btn-teal"
|
classes = "btn btn-green"
|
||||||
) { +"Edit" }
|
) { +"Edit" }
|
||||||
form(method = FormMethod.post, classes = "inline") {
|
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"
|
||||||
|
) {
|
||||||
|
+"Private"
|
||||||
|
}
|
||||||
|
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-r bg-teal-200 text-gray-800"
|
||||||
|
) {
|
||||||
|
+"Public"
|
||||||
|
}
|
||||||
|
}
|
||||||
button(
|
button(
|
||||||
type = ButtonType.submit,
|
type = ButtonType.submit,
|
||||||
name = "delete",
|
name = "delete",
|
||||||
@@ -179,23 +195,4 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
|||||||
) { +"Delete" }
|
) { +"Delete" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun DIV.publicPrivateForm(note: PersistedNote) {
|
|
||||||
span("flex space-x-2 justify-end mb-4") {
|
|
||||||
|
|
||||||
form(method = FormMethod.post, classes = "ml-auto ") {
|
|
||||||
button(
|
|
||||||
type = ButtonType.submit,
|
|
||||||
name = if (note.public) "private" else "public",
|
|
||||||
classes = "btn btn-teal"
|
|
||||||
) {
|
|
||||||
if (note.public)
|
|
||||||
+"This note is public, do you want to make it private ?"
|
|
||||||
else
|
|
||||||
+"This note is private, do you want to make it public ?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -26,14 +26,32 @@ class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section("m-4 p-4 bg-gray-800 rounded") {
|
section("m-4 p-2 bg-gray-800 rounded flex flex-wrap justify-around items-end") {
|
||||||
p(classes = "mb-4") {
|
|
||||||
+"Export all my data"
|
form(classes = "m-2", method = FormMethod.post, action = "/export") {
|
||||||
|
button(name = "display",
|
||||||
|
classes = "inline btn btn-teal block",
|
||||||
|
type = submit) { +"Display my data" }
|
||||||
}
|
}
|
||||||
|
|
||||||
form(method = FormMethod.post, action = "/export") {
|
form(classes = "m-2", method = FormMethod.post, action = "/export") {
|
||||||
button(name = "display", classes = "inline btn btn-teal block", type = submit) { +"Display my data" }
|
|
||||||
button(name = "download", classes = "inline btn btn-green block ml-2 mt-2", type = submit) {
|
div {
|
||||||
|
listOf("json", "zip").forEach { format ->
|
||||||
|
radioInput(name = "format") {
|
||||||
|
id = format
|
||||||
|
attributes["value"] = format
|
||||||
|
if (format == "json") attributes["checked"] = ""
|
||||||
|
else attributes["class"] = "ml-4"
|
||||||
|
}
|
||||||
|
label(classes = "ml-2") {
|
||||||
|
attributes["for"] = format
|
||||||
|
+format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button(name = "download", classes = "inline btn btn-green block mt-2", type = submit) {
|
||||||
+"Download my data"
|
+"Download my data"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ abstract class View(staticFileResolver: StaticFileResolver) {
|
|||||||
attributes["crossorigin"] = "anonymous"
|
attributes["crossorigin"] = "anonymous"
|
||||||
}
|
}
|
||||||
link(rel = "stylesheet", href = styles)
|
link(rel = "stylesheet", href = styles)
|
||||||
link(rel = "shortcut icon", href = "/favicon.ico", type = "image/x-icon")
|
icons()
|
||||||
scripts.forEach { src ->
|
scripts.forEach { src ->
|
||||||
script(src = src) {}
|
script(src = src) {}
|
||||||
}
|
}
|
||||||
@@ -42,4 +42,15 @@ abstract class View(staticFileResolver: StaticFileResolver) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun HEAD.icons() {
|
||||||
|
link(rel = "apple-touch-icon", href = "/apple-touch-icon.png") { attributes["sizes"] = "180x180" }
|
||||||
|
link(rel = "icon", href = "/favicon-32x32.png", type = "image/png") { attributes["sizes"] = "32x32" }
|
||||||
|
link(rel = "icon", href = "/favicon-16x16.png", type = "image/png") { attributes["sizes"] = "16x16" }
|
||||||
|
link(rel = "manifest", href = "/site.webmanifest")
|
||||||
|
link(rel = "mask-icon", href = "/safari-pinned-tab.svg") { attributes["color"] = "#2c7a7b" }
|
||||||
|
meta(name = "msapplication-TileColor", content = "#00aba9")
|
||||||
|
meta(name = "theme-color", content = "#2c7a7b")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.app.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import be.simplenotes.app.utils.toTimeAgo
|
import be.simplenotes.app.utils.toTimeAgo
|
||||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ButtonType.submit
|
import kotlinx.html.ButtonType.submit
|
||||||
import kotlinx.html.FormMethod.post
|
import kotlinx.html.FormMethod.post
|
||||||
@@ -25,8 +25,8 @@ fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("over
|
|||||||
td("text-center") { +updatedAt.toTimeAgo() }
|
td("text-center") { +updatedAt.toTimeAgo() }
|
||||||
td { tags(tags) }
|
td { tags(tags) }
|
||||||
td("text-center") {
|
td("text-center") {
|
||||||
form(classes = "inline", method = post, action = "/notes/deleted/$uuid") {
|
form(method = post, action = "/notes/deleted/$uuid") {
|
||||||
button(classes = "btn btn-red", type = submit, name = "delete") {
|
button(classes = "btn btn-red mb-2", type = submit, name = "delete") {
|
||||||
+"Delete permanently"
|
+"Delete permanently"
|
||||||
}
|
}
|
||||||
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
|
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.app.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import be.simplenotes.app.utils.toTimeAgo
|
import be.simplenotes.app.utils.toTimeAgo
|
||||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ThScope.col
|
import kotlinx.html.ThScope.col
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#00aba9</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
|
After Width: | Height: | Size: 814 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1255 6993 c-179 -23 -313 -62 -461 -133 -396 -187 -665 -533 -766
|
||||||
|
-981 l-23 -104 0 -2275 0 -2275 23 -102 c125 -565 530 -970 1095 -1095 l102
|
||||||
|
-23 2275 0 2275 0 102 23 c565 125 970 530 1095 1095 l23 102 0 2275 0 2275
|
||||||
|
-23 102 c-125 566 -521 964 -1090 1095 l-97 22 -2250 2 c-1237 1 -2263 -1
|
||||||
|
-2280 -3z m1024 -1979 c128 -18 287 -70 394 -127 262 -139 448 -395 472 -649
|
||||||
|
3 -34 8 -78 11 -95 l5 -33 -255 0 -254 0 -6 28 c-2 15 -7 44 -11 65 -27 168
|
||||||
|
-204 335 -416 393 -94 26 -317 24 -421 -4 -218 -59 -345 -196 -356 -384 -6
|
||||||
|
-105 16 -173 80 -243 81 -88 227 -148 563 -230 509 -123 742 -228 916 -412
|
||||||
|
123 -131 170 -248 176 -448 9 -245 -55 -420 -212 -580 -162 -165 -387 -270
|
||||||
|
-667 -311 -154 -22 -461 -15 -595 15 -280 62 -513 193 -662 373 -72 85 -162
|
||||||
|
262 -185 358 -8 36 -18 95 -22 133 l-6 67 254 0 255 0 6 -53 c22 -179 135
|
||||||
|
-332 310 -417 127 -61 195 -74 387 -75 195 0 279 16 399 76 179 89 260 234
|
||||||
|
231 415 -22 137 -98 231 -243 302 -109 55 -202 84 -447 142 -237 57 -306 76
|
||||||
|
-427 120 -340 125 -535 303 -600 550 -23 87 -23 271 0 360 87 335 409 595 827
|
||||||
|
665 108 19 371 18 499 -1z m3170 0 c202 -35 325 -95 453 -224 77 -77 93 -100
|
||||||
|
141 -201 30 -63 63 -143 72 -179 49 -183 48 -159 52 -1307 l4 -1083 -256 0
|
||||||
|
-255 0 0 1034 c0 1160 1 1133 -70 1282 -92 192 -259 271 -548 262 -117 -4
|
||||||
|
-149 -9 -221 -33 -177 -60 -340 -200 -440 -376 l-31 -56 0 -1056 0 -1057 -255
|
||||||
|
0 -255 0 0 1480 0 1480 239 0 238 0 6 -82 c4 -46 7 -120 7 -165 0 -45 3 -88 6
|
||||||
|
-97 4 -11 28 8 92 72 163 164 356 267 575 307 103 19 334 18 446 -1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "SimpleNotes",
|
||||||
|
"short_name": "SimpleNotes",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package be.simplenotes.app.filters
|
|||||||
import be.simplenotes.domain.security.JwtPayload
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||||
import be.simplenotes.domain.security.SimpleJwt
|
import be.simplenotes.domain.security.SimpleJwt
|
||||||
import be.simplenotes.shared.config.JwtConfig
|
import be.simplenotes.config.JwtConfig
|
||||||
import com.natpryce.hamkrest.assertion.assertThat
|
import com.natpryce.hamkrest.assertion.assertThat
|
||||||
import org.http4k.core.*
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Method.GET
|
import org.http4k.core.Method.GET
|
||||||
@@ -27,8 +27,8 @@ internal class AuthFilterTest {
|
|||||||
private val simpleJwt = SimpleJwt(jwtConfig)
|
private val simpleJwt = SimpleJwt(jwtConfig)
|
||||||
private val extractor = JwtPayloadExtractor(simpleJwt)
|
private val extractor = JwtPayloadExtractor(simpleJwt)
|
||||||
private val ctx = RequestContexts()
|
private val ctx = RequestContexts()
|
||||||
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)()
|
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)
|
||||||
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)()
|
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)
|
||||||
|
|
||||||
private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) }
|
private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) }
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.app.utils
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
import be.simplenotes.domain.usecases.search.SearchTerms
|
import be.simplenotes.search.SearchTerms
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
import org.junit.jupiter.params.provider.MethodSource
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>simplenotes-parent</artifactId>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>simplenotes-config</artifactId>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.simplenotes.shared.config
|
package be.simplenotes.config
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>simplenotes-parent</artifactId>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>simplenotes-domain</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<artifactId>simplenotes-config</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<artifactId>simplenotes-test-resources</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.koin</groupId>
|
||||||
|
<artifactId>koin-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.arrow-kt</groupId>
|
||||||
|
<artifactId>arrow-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.natpryce</groupId>
|
||||||
|
<artifactId>hamkrest</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.mockk</groupId>
|
||||||
|
<artifactId>mockk</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.konform</groupId>
|
||||||
|
<artifactId>konform-jvm</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mindrot</groupId>
|
||||||
|
<artifactId>jbcrypt</artifactId>
|
||||||
|
<version>0.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>3.10.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.vladsch.flexmark</groupId>
|
||||||
|
<artifactId>flexmark</artifactId>
|
||||||
|
<version>0.62.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.vladsch.flexmark</groupId>
|
||||||
|
<artifactId>flexmark-ext-gfm-tasklist</artifactId>
|
||||||
|
<version>0.62.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.26</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
|
||||||
|
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||||
|
<version>20200713.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-compress</artifactId>
|
||||||
|
<version>1.20</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<artifactId>simplenotes-types</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<artifactId>simplenotes-persistance</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>be.simplenotes</groupId>
|
||||||
|
<artifactId>simplenotes-search</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -26,7 +26,12 @@ val domainModule = module {
|
|||||||
single<PasswordHash> { BcryptPasswordHash() }
|
single<PasswordHash> { BcryptPasswordHash() }
|
||||||
single { SimpleJwt(get()) }
|
single { SimpleJwt(get()) }
|
||||||
single { JwtPayloadExtractor(get()) }
|
single { JwtPayloadExtractor(get()) }
|
||||||
single { NoteService(get(), get(), get(), get()) }
|
single {
|
||||||
|
NoteService(get(), get(), get(), get()).apply {
|
||||||
|
dropAllIndexes()
|
||||||
|
indexAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
single<MarkdownConverter> { MarkdownConverterImpl() }
|
single<MarkdownConverter> { MarkdownConverterImpl() }
|
||||||
single<ExportUseCase> { ExportUseCaseImpl(get()) }
|
single<ExportUseCase> { ExportUseCaseImpl(get(), get()) }
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ package be.simplenotes.domain.security
|
|||||||
|
|
||||||
import org.owasp.html.HtmlPolicyBuilder
|
import org.owasp.html.HtmlPolicyBuilder
|
||||||
|
|
||||||
object HtmlSanitizer {
|
internal object HtmlSanitizer {
|
||||||
private val htmlPolicy = HtmlPolicyBuilder()
|
private val htmlPolicy = HtmlPolicyBuilder()
|
||||||
.allowElements("a")
|
.allowElements("a")
|
||||||
.allowCommonBlockElements()
|
.allowCommonBlockElements()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.domain.model.PersistedUser
|
import be.simplenotes.types.PersistedUser
|
||||||
import com.auth0.jwt.exceptions.JWTVerificationException
|
import com.auth0.jwt.exceptions.JWTVerificationException
|
||||||
|
|
||||||
data class JwtPayload(val userId: Int, val username: String) {
|
data class JwtPayload(val userId: Int, val username: String) {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.shared.config.JwtConfig
|
import be.simplenotes.config.JwtConfig
|
||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.JWTVerifier
|
import com.auth0.jwt.JWTVerifier
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
@@ -2,17 +2,16 @@ package be.simplenotes.domain.usecases
|
|||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.extensions.fx
|
import arrow.core.extensions.fx
|
||||||
import be.simplenotes.domain.model.Note
|
import be.simplenotes.types.Note
|
||||||
import be.simplenotes.domain.model.PersistedNote
|
import be.simplenotes.types.PersistedNote
|
||||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
import be.simplenotes.types.PersistedNoteMetadata
|
||||||
import be.simplenotes.domain.security.HtmlSanitizer
|
import be.simplenotes.domain.security.HtmlSanitizer
|
||||||
import be.simplenotes.domain.usecases.markdown.MarkdownConverter
|
import be.simplenotes.domain.usecases.markdown.MarkdownConverter
|
||||||
import be.simplenotes.domain.usecases.markdown.MarkdownParsingError
|
import be.simplenotes.domain.usecases.markdown.MarkdownParsingError
|
||||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
import be.simplenotes.persistance.repositories.NoteRepository
|
||||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
import be.simplenotes.persistance.repositories.UserRepository
|
||||||
import be.simplenotes.domain.usecases.search.NoteSearcher
|
import be.simplenotes.search.NoteSearcher
|
||||||
import be.simplenotes.domain.usecases.search.SearchTerms
|
import be.simplenotes.search.SearchTerms
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class NoteService(
|
class NoteService(
|
||||||