diff --git a/pom.xml b/pom.xml
index b2ec063..d4efa06 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
simplenotes-types
simplenotes-config
simplenotes-test-resources
+ simplenotes-views
pom
diff --git a/simplenotes-app/pom.xml b/simplenotes-app/pom.xml
index 29e7a4d..903eb0e 100644
--- a/simplenotes-app/pom.xml
+++ b/simplenotes-app/pom.xml
@@ -65,20 +65,10 @@
compile
-
- org.jetbrains.kotlinx
- kotlinx-html-jvm
- 0.7.1
-
org.jetbrains.kotlinx
kotlinx-serialization-json-jvm
-
- org.ocpsoft.prettytime
- prettytime
- 4.0.5.Final
-
ch.qos.logback
@@ -112,6 +102,12 @@
me.liuwj.ktorm
ktorm-core
+
+ be.simplenotes
+ simplenotes-views
+ 1.0-SNAPSHOT
+ compile
+
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/SimpleNotes.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/SimpleNotes.kt
index bd68ec0..836802c 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/SimpleNotes.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/SimpleNotes.kt
@@ -6,6 +6,7 @@ import be.simplenotes.domain.domainModule
import be.simplenotes.persistance.migrationModule
import be.simplenotes.persistance.persistanceModule
import be.simplenotes.search.searchModule
+import be.simplenotes.views.viewModule
import org.koin.core.context.startKoin
import org.koin.core.context.unloadKoinModules
@@ -16,10 +17,8 @@ fun main() {
persistanceModule,
migrationModule,
configModule,
- baseModule,
- userModule,
- noteModule,
- settingsModule,
+ viewModule,
+ controllerModule,
domainModule,
searchModule,
apiModule,
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/api/ApiNoteController.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/api/ApiNoteController.kt
index c874389..2705d8f 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/api/ApiNoteController.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/api/ApiNoteController.kt
@@ -4,8 +4,8 @@ import be.simplenotes.app.extensions.auto
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
-import be.simplenotes.domain.security.JwtPayload
import be.simplenotes.domain.usecases.NoteService
+import be.simplenotes.types.LoggedInUser
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -20,27 +20,27 @@ import java.util.*
class ApiNoteController(private val noteService: NoteService, private val json: Json) {
- fun createNote(request: Request, jwtPayload: JwtPayload): Response {
+ fun createNote(request: Request, loggedInUser: LoggedInUser): Response {
val content = noteContentLens(request)
- return noteService.create(jwtPayload.userId, content).fold(
+ return noteService.create(loggedInUser.userId, content).fold(
{ Response(BAD_REQUEST) },
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
)
}
- fun notes(request: Request, jwtPayload: JwtPayload): Response {
- val notes = noteService.paginatedNotes(jwtPayload.userId, page = 1).notes
+ fun notes(request: Request, loggedInUser: LoggedInUser): Response {
+ val notes = noteService.paginatedNotes(loggedInUser.userId, page = 1).notes
return persistedNotesMetadataLens(notes, Response(OK))
}
- fun note(request: Request, jwtPayload: JwtPayload): Response =
- noteService.find(jwtPayload.userId, uuidLens(request))
+ fun note(request: Request, loggedInUser: LoggedInUser): Response =
+ noteService.find(loggedInUser.userId, uuidLens(request))
?.let { persistedNoteLens(it, Response(OK)) }
?: Response(NOT_FOUND)
- fun update(request: Request, jwtPayload: JwtPayload): Response {
+ fun update(request: Request, loggedInUser: LoggedInUser): Response {
val content = noteContentLens(request)
- return noteService.update(jwtPayload.userId, uuidLens(request), content).fold({
+ return noteService.update(loggedInUser.userId, uuidLens(request), content).fold({
Response(BAD_REQUEST)
}, {
if (it == null) Response(NOT_FOUND)
@@ -48,10 +48,10 @@ class ApiNoteController(private val noteService: NoteService, private val json:
})
}
- fun search(request: Request, jwtPayload: JwtPayload): Response {
+ fun search(request: Request, loggedInUser: LoggedInUser): Response {
val query = searchContentLens(request)
val terms = parseSearchTerms(query)
- val notes = noteService.search(jwtPayload.userId, terms)
+ val notes = noteService.search(loggedInUser.userId, terms)
return persistedNotesMetadataLens(notes, Response(OK))
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/BaseController.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/BaseController.kt
index 37e5134..a63cd09 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/BaseController.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/BaseController.kt
@@ -1,13 +1,13 @@
package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
-import be.simplenotes.app.views.BaseView
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
+import be.simplenotes.views.BaseView
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
class BaseController(private val view: BaseView) {
- fun index(@Suppress("UNUSED_PARAMETER") request: Request, jwtPayload: JwtPayload?) =
- Response(OK).html(view.renderHome(jwtPayload))
+ fun index(@Suppress("UNUSED_PARAMETER") request: Request, loggedInUser: LoggedInUser?) =
+ Response(OK).html(view.renderHome(loggedInUser))
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/NoteController.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/NoteController.kt
index 9c3d326..e3e25c4 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/NoteController.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/NoteController.kt
@@ -3,12 +3,12 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.redirect
import be.simplenotes.app.utils.parseSearchTerms
-import be.simplenotes.app.views.NoteView
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.views.NoteView
import be.simplenotes.domain.usecases.NoteService
import be.simplenotes.domain.usecases.markdown.InvalidMeta
import be.simplenotes.domain.usecases.markdown.MissingMeta
import be.simplenotes.domain.usecases.markdown.ValidationError
+import be.simplenotes.types.LoggedInUser
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
@@ -25,18 +25,18 @@ class NoteController(
private val noteService: NoteService,
) {
- fun new(request: Request, jwtPayload: JwtPayload): Response {
- if (request.method == Method.GET) return Response(OK).html(view.noteEditor(jwtPayload))
+ fun new(request: Request, loggedInUser: LoggedInUser): Response {
+ if (request.method == Method.GET) return Response(OK).html(view.noteEditor(loggedInUser))
val markdownForm = request.form("markdown") ?: ""
- return noteService.create(jwtPayload.userId, markdownForm).fold(
+ return noteService.create(loggedInUser.userId, markdownForm).fold(
{
val html = when (it) {
- MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
- InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
+ MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
+ InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
is ValidationError -> view.noteEditor(
- jwtPayload,
+ loggedInUser,
validationErrors = it.validationErrors,
textarea = markdownForm
)
@@ -49,66 +49,66 @@ class NoteController(
)
}
- fun list(request: Request, jwtPayload: JwtPayload): Response {
+ fun list(request: Request, loggedInUser: LoggedInUser): Response {
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
val tag = request.query("tag")
- val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag)
- val deletedCount = noteService.countDeleted(jwtPayload.userId)
- return Response(OK).html(view.notes(jwtPayload, notes, currentPage, pages, deletedCount, tag = tag))
+ val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag)
+ val deletedCount = noteService.countDeleted(loggedInUser.userId)
+ return Response(OK).html(view.notes(loggedInUser, notes, currentPage, pages, deletedCount, tag = tag))
}
- fun search(request: Request, jwtPayload: JwtPayload): Response {
+ fun search(request: Request, loggedInUser: LoggedInUser): Response {
val query = request.form("search") ?: ""
val terms = parseSearchTerms(query)
- val notes = noteService.search(jwtPayload.userId, terms)
- val deletedCount = noteService.countDeleted(jwtPayload.userId)
- return Response(OK).html(view.search(jwtPayload, notes, query, deletedCount))
+ val notes = noteService.search(loggedInUser.userId, terms)
+ val deletedCount = noteService.countDeleted(loggedInUser.userId)
+ return Response(OK).html(view.search(loggedInUser, notes, query, deletedCount))
}
- fun note(request: Request, jwtPayload: JwtPayload): Response {
+ fun note(request: Request, loggedInUser: LoggedInUser): Response {
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
if (request.method == Method.POST) {
if (request.form("delete") != null) {
- return if (noteService.trash(jwtPayload.userId, noteUuid))
+ return if (noteService.trash(loggedInUser.userId, noteUuid))
Response.redirect("/notes") // TODO: flash cookie to show success ?
else
Response(NOT_FOUND) // TODO: show an error
}
if (request.form("public") != null) {
- if (!noteService.makePublic(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
+ if (!noteService.makePublic(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
} else if (request.form("private") != null) {
- if (!noteService.makePrivate(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
+ if (!noteService.makePrivate(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
}
}
- val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
- return Response(OK).html(view.renderedNote(jwtPayload, note, shared = false))
+ val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
+ return Response(OK).html(view.renderedNote(loggedInUser, note, shared = false))
}
- fun public(request: Request, jwtPayload: JwtPayload?): Response {
+ fun public(request: Request, loggedInUser: LoggedInUser?): Response {
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
val note = noteService.findPublic(noteUuid) ?: return Response(NOT_FOUND)
- return Response(OK).html(view.renderedNote(jwtPayload, note, shared = true))
+ return Response(OK).html(view.renderedNote(loggedInUser, note, shared = true))
}
- fun edit(request: Request, jwtPayload: JwtPayload): Response {
+ fun edit(request: Request, loggedInUser: LoggedInUser): Response {
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
- val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
+ val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
if (request.method == Method.GET) {
- return Response(OK).html(view.noteEditor(jwtPayload, textarea = note.markdown))
+ return Response(OK).html(view.noteEditor(loggedInUser, textarea = note.markdown))
}
val markdownForm = request.form("markdown") ?: ""
- return noteService.update(jwtPayload.userId, note.uuid, markdownForm).fold(
+ return noteService.update(loggedInUser.userId, note.uuid, markdownForm).fold(
{
val html = when (it) {
- MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
- InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
+ MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
+ InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
is ValidationError -> view.noteEditor(
- jwtPayload,
+ loggedInUser,
validationErrors = it.validationErrors,
textarea = markdownForm
)
@@ -121,21 +121,21 @@ class NoteController(
)
}
- fun trash(request: Request, jwtPayload: JwtPayload): Response {
+ fun trash(request: Request, loggedInUser: LoggedInUser): Response {
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
val tag = request.query("tag")
- val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag, deleted = true)
- return Response(OK).html(view.trash(jwtPayload, notes, currentPage, pages))
+ val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag, deleted = true)
+ return Response(OK).html(view.trash(loggedInUser, notes, currentPage, pages))
}
- fun deleted(request: Request, jwtPayload: JwtPayload): Response {
+ fun deleted(request: Request, loggedInUser: LoggedInUser): Response {
val uuid = request.uuidPath() ?: return Response(NOT_FOUND)
return if (request.form("delete") != null)
- if (noteService.delete(jwtPayload.userId, uuid))
+ if (noteService.delete(loggedInUser.userId, uuid))
Response.redirect("/notes/trash")
else
Response(NOT_FOUND)
- else if (noteService.restore(jwtPayload.userId, uuid))
+ else if (noteService.restore(loggedInUser.userId, uuid))
Response.redirect("/notes/$uuid")
else
Response(NOT_FOUND)
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/SettingsController.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/SettingsController.kt
index 51d5d6a..9e16ee3 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/SettingsController.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/SettingsController.kt
@@ -2,11 +2,11 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.redirect
-import be.simplenotes.app.views.SettingView
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.views.SettingView
import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.delete.DeleteError
import be.simplenotes.domain.usecases.users.delete.DeleteForm
+import be.simplenotes.types.LoggedInUser
import org.http4k.core.*
import org.http4k.core.body.form
import org.http4k.core.cookie.invalidateCookie
@@ -15,11 +15,11 @@ class SettingsController(
private val userService: UserService,
private val settingView: SettingView,
) {
- fun settings(request: Request, jwtPayload: JwtPayload): Response {
+ fun settings(request: Request, loggedInUser: LoggedInUser): Response {
if (request.method == Method.GET)
- return Response(Status.OK).html(settingView.settings(jwtPayload))
+ return Response(Status.OK).html(settingView.settings(loggedInUser))
- val deleteForm = request.deleteForm(jwtPayload)
+ val deleteForm = request.deleteForm(loggedInUser)
val result = userService.delete(deleteForm)
return result.fold(
@@ -28,13 +28,13 @@ class SettingsController(
DeleteError.Unregistered -> Response.redirect("/").invalidateCookie("Bearer")
DeleteError.WrongPassword -> Response(Status.OK).html(
settingView.settings(
- jwtPayload,
+ loggedInUser,
error = "Wrong password"
)
)
is DeleteError.InvalidForm -> Response(Status.OK).html(
settingView.settings(
- jwtPayload,
+ loggedInUser,
validationErrors = it.validationErrors
)
)
@@ -53,23 +53,23 @@ class SettingsController(
.header("Content-Type", contentType)
}
- fun export(request: Request, jwtPayload: JwtPayload): Response {
+ fun export(request: Request, loggedInUser: LoggedInUser): Response {
val isDownload = request.form("download") != null
return if (isDownload) {
- val filename = "simplenotes-export-${jwtPayload.username}"
+ val filename = "simplenotes-export-${loggedInUser.username}"
if (request.form("format") == "zip") {
- val zip = userService.exportAsZip(jwtPayload.userId)
+ val zip = userService.exportAsZip(loggedInUser.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")
+ .body(userService.exportAsJson(loggedInUser.userId))
+ } else Response(Status.OK).body(userService.exportAsJson(loggedInUser.userId)).header("Content-Type", "application/json")
}
- private fun Request.deleteForm(jwtPayload: JwtPayload) =
- DeleteForm(jwtPayload.username, form("password"), form("checked") != null)
+ private fun Request.deleteForm(loggedInUser: LoggedInUser) =
+ DeleteForm(loggedInUser.username, form("password"), form("checked") != null)
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/UserController.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/UserController.kt
index d223955..bb98ed9 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/UserController.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/controllers/UserController.kt
@@ -3,14 +3,14 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.isSecure
import be.simplenotes.app.extensions.redirect
-import be.simplenotes.app.views.UserView
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.views.UserView
import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.login.*
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
import be.simplenotes.domain.usecases.users.register.RegisterForm
import be.simplenotes.domain.usecases.users.register.UserExists
import be.simplenotes.config.JwtConfig
+import be.simplenotes.types.LoggedInUser
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
@@ -27,9 +27,9 @@ class UserController(
private val userView: UserView,
private val jwtConfig: JwtConfig,
) {
- fun register(request: Request, jwtPayload: JwtPayload?): Response {
+ fun register(request: Request, loggedInUser: LoggedInUser?): Response {
if (request.method == GET) return Response(OK).html(
- userView.register(jwtPayload)
+ userView.register(loggedInUser)
)
val result = userService.register(request.registerForm())
@@ -38,12 +38,12 @@ class UserController(
{
val html = when (it) {
UserExists -> userView.register(
- jwtPayload,
+ loggedInUser,
error = "User already exists"
)
is InvalidRegisterForm ->
userView.register(
- jwtPayload,
+ loggedInUser,
validationErrors = it.validationErrors
)
}
@@ -58,9 +58,9 @@ class UserController(
private fun Request.registerForm() = RegisterForm(form("username"), form("password"))
private fun Request.loginForm(): LoginForm = registerForm()
- fun login(request: Request, jwtPayload: JwtPayload?): Response {
+ fun login(request: Request, loggedInUser: LoggedInUser?): Response {
if (request.method == GET) return Response(OK).html(
- userView.login(jwtPayload)
+ userView.login(loggedInUser)
)
val result = userService.login(request.loginForm())
@@ -70,17 +70,17 @@ class UserController(
val html = when (it) {
Unregistered ->
userView.login(
- jwtPayload,
+ loggedInUser,
error = "User does not exist"
)
WrongPassword ->
userView.login(
- jwtPayload,
+ loggedInUser,
error = "Wrong password"
)
is InvalidLoginForm ->
userView.login(
- jwtPayload,
+ loggedInUser,
validationErrors = it.validationErrors
)
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/AuthFilter.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/AuthFilter.kt
index 98f64f0..b080de4 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/AuthFilter.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/AuthFilter.kt
@@ -1,8 +1,8 @@
package be.simplenotes.app.filters
import be.simplenotes.app.extensions.redirect
-import be.simplenotes.domain.security.JwtPayload
import be.simplenotes.domain.security.JwtPayloadExtractor
+import be.simplenotes.types.LoggedInUser
import org.http4k.core.*
import org.http4k.core.Status.Companion.UNAUTHORIZED
import org.http4k.core.cookie.cookie
@@ -40,7 +40,7 @@ class AuthFilter(
}
}
-fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
+fun Request.jwtPayload(ctx: RequestContexts): LoggedInUser? = ctx[this][authKey]
enum class JwtSource {
Header, Cookie
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/ErrorFilter.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/ErrorFilter.kt
index 21f8ece..a80eb1d 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/ErrorFilter.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/filters/ErrorFilter.kt
@@ -1,8 +1,8 @@
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 be.simplenotes.views.ErrorView
+import be.simplenotes.views.ErrorView.Type.*
import org.http4k.core.*
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
import org.http4k.core.Status.Companion.NOT_FOUND
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ConfigModule.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ConfigModule.kt
index b860224..4699451 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ConfigModule.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ConfigModule.kt
@@ -1,6 +1,8 @@
package be.simplenotes.app.modules
import be.simplenotes.app.Config
+import be.simplenotes.app.utils.StaticFileResolver
+import org.koin.core.qualifier.named
import org.koin.dsl.module
val configModule = module {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/CoreModules.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/CoreModules.kt
index 8961883..82f2f6c 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/CoreModules.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/CoreModules.kt
@@ -1,29 +1,12 @@
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 {
+val controllerModule = 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()) }
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ServerModule.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ServerModule.kt
index 421fa9f..0f5707f 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ServerModule.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/modules/ServerModule.kt
@@ -10,7 +10,7 @@ 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.views.ErrorView
import be.simplenotes.config.ServerConfig
import org.eclipse.jetty.server.ServerConnector
import org.http4k.core.Filter
@@ -59,5 +59,5 @@ val serverModule = module {
single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
single { ErrorFilter(get()) }
single { TransactionFilter(get()) }
- single { ErrorView(get()) }
+ single(named("styles")) { get().resolve("styles.css") }
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/routes/Router.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/routes/Router.kt
index 4caf064..d0862f4 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/routes/Router.kt
+++ b/simplenotes-app/src/main/kotlin/be/simplenotes/app/routes/Router.kt
@@ -4,7 +4,7 @@ import be.simplenotes.app.api.ApiNoteController
import be.simplenotes.app.api.ApiUserController
import be.simplenotes.app.controllers.*
import be.simplenotes.app.filters.*
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
import org.http4k.core.*
import org.http4k.core.Method.*
import org.http4k.filter.ResponseFilters.GZip
@@ -102,5 +102,5 @@ class Router(
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
}
-private typealias PublicHandler = (Request, JwtPayload?) -> Response
-private typealias ProtectedHandler = (Request, JwtPayload) -> Response
+private typealias PublicHandler = (Request, LoggedInUser?) -> Response
+private typealias ProtectedHandler = (Request, LoggedInUser) -> Response
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/utils/PrettyDate.kt b/simplenotes-app/src/main/kotlin/be/simplenotes/app/utils/PrettyDate.kt
deleted file mode 100644
index 9294e9a..0000000
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/utils/PrettyDate.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package be.simplenotes.app.utils
-
-import org.ocpsoft.prettytime.PrettyTime
-import java.time.LocalDateTime
-import java.time.ZoneId
-import java.util.*
-
-private val prettyTime = PrettyTime()
-
-fun LocalDateTime.toTimeAgo(): String = prettyTime.format(Date.from(atZone(ZoneId.systemDefault()).toInstant()))
diff --git a/simplenotes-app/src/test/kotlin/be/simplenotes/app/filters/AuthFilterTest.kt b/simplenotes-app/src/test/kotlin/be/simplenotes/app/filters/AuthFilterTest.kt
index 929542d..30ecc74 100644
--- a/simplenotes-app/src/test/kotlin/be/simplenotes/app/filters/AuthFilterTest.kt
+++ b/simplenotes-app/src/test/kotlin/be/simplenotes/app/filters/AuthFilterTest.kt
@@ -1,9 +1,9 @@
package be.simplenotes.app.filters
-import be.simplenotes.domain.security.JwtPayload
import be.simplenotes.domain.security.JwtPayloadExtractor
import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.config.JwtConfig
+import be.simplenotes.types.LoggedInUser
import com.natpryce.hamkrest.assertion.assertThat
import org.http4k.core.*
import org.http4k.core.Method.GET
@@ -58,7 +58,7 @@ internal class AuthFilterTest {
@Test
fun `it should allow a valid token`() {
- val jwtPayload = JwtPayload(1, "user")
+ val jwtPayload = LoggedInUser(1, "user")
val token = simpleJwt.sign(jwtPayload)
val response = app(Request(GET, "/optional").cookie("Bearer", token))
assertThat(response, hasStatus(OK))
@@ -84,7 +84,7 @@ internal class AuthFilterTest {
@Test
fun `it should allow a valid token"`() {
- val jwtPayload = JwtPayload(1, "user")
+ val jwtPayload = LoggedInUser(1, "user")
val token = simpleJwt.sign(jwtPayload)
val response = app(Request(GET, "/protected").cookie("Bearer", token))
assertThat(response, hasStatus(OK))
diff --git a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/JwtPayload.kt b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/JwtPayload.kt
index b90f2ff..e316bdc 100644
--- a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/JwtPayload.kt
+++ b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/JwtPayload.kt
@@ -1,18 +1,14 @@
package be.simplenotes.domain.security
-import be.simplenotes.types.PersistedUser
+import be.simplenotes.types.LoggedInUser
import com.auth0.jwt.exceptions.JWTVerificationException
-data class JwtPayload(val userId: Int, val username: String) {
- constructor(user: PersistedUser) : this(user.id, user.username)
-}
-
class JwtPayloadExtractor(private val jwt: SimpleJwt) {
- operator fun invoke(token: String): JwtPayload? = try {
+ operator fun invoke(token: String): LoggedInUser? = try {
val decodedJWT = jwt.verifier.verify(token)
val id = decodedJWT.getClaim(userIdField).asInt() ?: null
val username = decodedJWT.getClaim(usernameField).asString() ?: null
- id?.let { username?.let { JwtPayload(id, username) } }
+ id?.let { username?.let { LoggedInUser(id, username) } }
} catch (e: JWTVerificationException) {
null
} catch (e: IllegalArgumentException) {
diff --git a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/SimpleJwt.kt b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/SimpleJwt.kt
index 0fde615..e8ced0f 100644
--- a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/SimpleJwt.kt
+++ b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/security/SimpleJwt.kt
@@ -1,6 +1,7 @@
package be.simplenotes.domain.security
import be.simplenotes.config.JwtConfig
+import be.simplenotes.types.LoggedInUser
import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
@@ -15,9 +16,9 @@ class SimpleJwt(jwtConfig: JwtConfig) {
private val algorithm = Algorithm.HMAC256(jwtConfig.secret)
val verifier: JWTVerifier = JWT.require(algorithm).build()
- fun sign(jwtPayload: JwtPayload): String = JWT.create()
- .withClaim(userIdField, jwtPayload.userId)
- .withClaim(usernameField, jwtPayload.username)
+ fun sign(loggedInUser: LoggedInUser): String = JWT.create()
+ .withClaim(userIdField, loggedInUser.userId)
+ .withClaim(usernameField, loggedInUser.username)
.withExpiresAt(getExpiration())
.sign(algorithm)
diff --git a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/usecases/users/login/LoginUseCaseImpl.kt b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/usecases/users/login/LoginUseCaseImpl.kt
index cfef653..2190dac 100644
--- a/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/usecases/users/login/LoginUseCaseImpl.kt
+++ b/simplenotes-domain/src/main/kotlin/be/simplenotes/domain/usecases/users/login/LoginUseCaseImpl.kt
@@ -4,11 +4,11 @@ import arrow.core.Either
import arrow.core.extensions.fx
import arrow.core.filterOrElse
import arrow.core.rightIfNotNull
-import be.simplenotes.domain.security.JwtPayload
import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistance.repositories.UserRepository
+import be.simplenotes.types.LoggedInUser
internal class LoginUseCaseImpl(
private val userRepository: UserRepository,
@@ -20,6 +20,6 @@ internal class LoginUseCaseImpl(
!userRepository.find(user.username)
.rightIfNotNull { Unregistered }
.filterOrElse({ passwordHash.verify(form.password!!, it.password) }, { WrongPassword })
- .map { jwt.sign(JwtPayload(it)) }
+ .map { jwt.sign(LoggedInUser(it)) }
}
}
diff --git a/simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/JwtPayloadExtractorTest.kt b/simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/LoggedInUserExtractorTest.kt
similarity index 91%
rename from simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/JwtPayloadExtractorTest.kt
rename to simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/LoggedInUserExtractorTest.kt
index 0f09b32..94b6116 100644
--- a/simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/JwtPayloadExtractorTest.kt
+++ b/simplenotes-domain/src/test/kotlin/be/simplenotes/domain/security/LoggedInUserExtractorTest.kt
@@ -2,6 +2,7 @@ package be.simplenotes.domain.security
import be.simplenotes.domain.usecases.users.login.Token
import be.simplenotes.config.JwtConfig
+import be.simplenotes.types.LoggedInUser
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.natpryce.hamkrest.absent
@@ -13,7 +14,7 @@ import org.junit.jupiter.params.provider.MethodSource
import java.util.concurrent.TimeUnit
import java.util.stream.Stream
-internal class JwtPayloadExtractorTest {
+internal class LoggedInUserExtractorTest {
private val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS)
private val simpleJwt = SimpleJwt(jwtConfig)
private val jwtPayloadExtractor = JwtPayloadExtractor(simpleJwt)
@@ -45,6 +46,6 @@ internal class JwtPayloadExtractorTest {
@Test
fun `parse valid token`() {
val token = createToken(username = "someone", id = 1)
- assertThat(jwtPayloadExtractor(token), equalTo(JwtPayload(1, "someone")))
+ assertThat(jwtPayloadExtractor(token), equalTo(LoggedInUser(1, "someone")))
}
}
diff --git a/simplenotes-types/src/main/kotlin/be/simplenotes/types/User.kt b/simplenotes-types/src/main/kotlin/be/simplenotes/types/User.kt
index bd93f58..14356cd 100644
--- a/simplenotes-types/src/main/kotlin/be/simplenotes/types/User.kt
+++ b/simplenotes-types/src/main/kotlin/be/simplenotes/types/User.kt
@@ -2,3 +2,6 @@ package be.simplenotes.types
data class User(val username: String, val password: String)
data class PersistedUser(val username: String, val password: String, val id: Int)
+data class LoggedInUser(val userId: Int, val username: String) {
+ constructor(user: PersistedUser) : this(user.id, user.username)
+}
diff --git a/simplenotes-views/pom.xml b/simplenotes-views/pom.xml
new file mode 100644
index 0000000..401cd17
--- /dev/null
+++ b/simplenotes-views/pom.xml
@@ -0,0 +1,41 @@
+
+
+
+ simplenotes-parent
+ be.simplenotes
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ simplenotes-views
+
+
+
+ org.koin
+ koin-core
+
+
+ io.konform
+ konform-jvm
+ 0.2.0
+
+
+ org.jetbrains.kotlinx
+ kotlinx-html-jvm
+ 0.7.1
+
+
+ org.ocpsoft.prettytime
+ prettytime
+ 4.0.5.Final
+
+
+ be.simplenotes
+ simplenotes-types
+ 1.0-SNAPSHOT
+
+
+
+
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/BaseView.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/BaseView.kt
similarity index 92%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/BaseView.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/BaseView.kt
index f911cf3..64f95ea 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/BaseView.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/BaseView.kt
@@ -1,15 +1,14 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
import kotlinx.html.*
import kotlinx.html.ThScope.col
-class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
- fun renderHome(jwtPayload: JwtPayload?) = renderPage(
+class BaseView(styles: String) : View(styles) {
+ fun renderHome(loggedInUser: LoggedInUser?) = renderPage(
title = "Home",
description = "A fast and simple note taking website",
- jwtPayload = jwtPayload
+ loggedInUser = loggedInUser
) {
section("text-center my-2 p-2") {
h1("text-5xl casual") {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/ErrorView.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/ErrorView.kt
similarity index 70%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/ErrorView.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/ErrorView.kt
index 4f6c830..58e1f50 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/ErrorView.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/ErrorView.kt
@@ -1,12 +1,11 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.app.views.components.Alert
-import be.simplenotes.app.views.components.alert
+import be.simplenotes.views.components.Alert
+import be.simplenotes.views.components.alert
import kotlinx.html.a
import kotlinx.html.div
-class ErrorView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
+class ErrorView(styles: String) : View(styles) {
enum class Type(val title: String) {
SqlTransientError("Database unavailable"),
@@ -14,7 +13,7 @@ class ErrorView(staticFileResolver: StaticFileResolver) : View(staticFileResolve
Other("Error"),
}
- fun error(errorType: Type) = renderPage(errorType.title, jwtPayload = null) {
+ fun error(errorType: Type) = renderPage(errorType.title, loggedInUser = null) {
div("container mx-auto p-4") {
when (errorType) {
Type.SqlTransientError -> alert(
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/NoteView.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/NoteView.kt
similarity index 88%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/NoteView.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/NoteView.kt
index ccfadb0..8c7fc30 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/NoteView.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/NoteView.kt
@@ -1,21 +1,21 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.app.views.components.*
+import be.simplenotes.types.LoggedInUser
+import be.simplenotes.views.components.noteListHeader
import be.simplenotes.types.PersistedNote
import be.simplenotes.types.PersistedNoteMetadata
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.views.components.*
import io.konform.validation.ValidationError
import kotlinx.html.*
-class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
+class NoteView(styles: String) : View(styles) {
fun noteEditor(
- jwtPayload: JwtPayload,
+ loggedInUser: LoggedInUser,
error: String? = null,
textarea: String? = null,
validationErrors: List = emptyList(),
- ) = renderPage(title = "New note", jwtPayload = jwtPayload) {
+ ) = renderPage(title = "New note", loggedInUser = loggedInUser) {
div("container mx-auto p-4") {
error?.let { alert(Alert.Warning, error) }
validationErrors.forEach {
@@ -46,13 +46,13 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
fun notes(
- jwtPayload: JwtPayload,
+ loggedInUser: LoggedInUser,
notes: List,
currentPage: Int,
numberOfPages: Int,
numberOfDeletedNotes: Int,
tag: String?,
- ) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
+ ) = renderPage(title = "Notes", loggedInUser = loggedInUser) {
div("container mx-auto p-4") {
noteListHeader(numberOfDeletedNotes)
if (notes.isNotEmpty())
@@ -68,11 +68,11 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
fun search(
- jwtPayload: JwtPayload,
+ loggedInUser: LoggedInUser,
notes: List,
query: String,
numberOfDeletedNotes: Int,
- ) = renderPage("Notes", jwtPayload = jwtPayload) {
+ ) = renderPage("Notes", loggedInUser = loggedInUser) {
div("container mx-auto p-4") {
noteListHeader(numberOfDeletedNotes, query)
noteTable(notes)
@@ -80,11 +80,11 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
fun trash(
- jwtPayload: JwtPayload,
+ loggedInUser: LoggedInUser,
notes: List,
currentPage: Int,
numberOfPages: Int,
- ) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
+ ) = renderPage(title = "Notes", loggedInUser = loggedInUser) {
div("container mx-auto p-4") {
div("flex justify-between mb-4") {
h1("text-2xl underline") { +"Deleted notes" }
@@ -116,9 +116,9 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
}
- fun renderedNote(jwtPayload: JwtPayload?, note: PersistedNote, shared: Boolean) = renderPage(
+ fun renderedNote(loggedInUser: LoggedInUser?, note: PersistedNote, shared: Boolean) = renderPage(
note.meta.title,
- jwtPayload = jwtPayload,
+ loggedInUser = loggedInUser,
scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js")
) {
div("container mx-auto p-4") {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/SettingView.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/SettingView.kt
similarity index 85%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/SettingView.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/SettingView.kt
index 05a313e..8c18203 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/SettingView.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/SettingView.kt
@@ -1,28 +1,27 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.extensions.summary
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.app.views.components.Alert
-import be.simplenotes.app.views.components.alert
-import be.simplenotes.app.views.components.input
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
+import be.simplenotes.views.components.Alert
+import be.simplenotes.views.components.alert
+import be.simplenotes.views.components.input
+import be.simplenotes.views.extensions.summary
import io.konform.validation.ValidationError
import kotlinx.html.*
import kotlinx.html.ButtonType.submit
-class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
+class SettingView(styles: String) : View(styles) {
fun settings(
- jwtPayload: JwtPayload,
+ loggedInUser: LoggedInUser,
error: String? = null,
validationErrors: List = emptyList(),
- ) = renderPage("Settings", jwtPayload = jwtPayload) {
+ ) = renderPage("Settings", loggedInUser = loggedInUser) {
div("container mx-auto") {
section("m-4 p-4 bg-gray-800 rounded") {
h1("text-xl") {
+"Welcome "
- span("text-teal-200 font-semibold") { +jwtPayload.username }
+ span("text-teal-200 font-semibold") { +loggedInUser.username }
}
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/UserView.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/UserView.kt
similarity index 77%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/UserView.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/UserView.kt
index 9574227..b8bbdf6 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/UserView.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/UserView.kt
@@ -1,23 +1,22 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.app.views.components.Alert
-import be.simplenotes.app.views.components.alert
-import be.simplenotes.app.views.components.input
-import be.simplenotes.app.views.components.submitButton
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
+import be.simplenotes.views.components.Alert
+import be.simplenotes.views.components.alert
+import be.simplenotes.views.components.input
+import be.simplenotes.views.components.submitButton
import io.konform.validation.ValidationError
import kotlinx.html.*
-class UserView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
+class UserView(styles: String) : View(styles) {
fun register(
- jwtPayload: JwtPayload?,
+ loggedInUser: LoggedInUser?,
error: String? = null,
validationErrors: List = emptyList(),
) = accountForm(
"Register",
"Registration page",
- jwtPayload,
+ loggedInUser,
error,
validationErrors,
"Create an account",
@@ -28,11 +27,11 @@ class UserView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
}
fun login(
- jwtPayload: JwtPayload?,
+ loggedInUser: LoggedInUser?,
error: String? = null,
validationErrors: List = emptyList(),
new: Boolean = false,
- ) = accountForm("Login", "Login page", jwtPayload, error, validationErrors, "Sign In", "Sign In", new) {
+ ) = accountForm("Login", "Login page", loggedInUser, error, validationErrors, "Sign In", "Sign In", new) {
+"Don't have an account yet? "
a(href = "/register", classes = "no-underline text-blue-500 hover:text-blue-400 font-bold") {
+"Create an account"
@@ -42,14 +41,14 @@ class UserView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
private fun accountForm(
title: String,
description: String,
- jwtPayload: JwtPayload?,
+ loggedInUser: LoggedInUser?,
error: String? = null,
validationErrors: List = emptyList(),
h1: String,
submit: String,
new: Boolean = false,
footer: FlowContent.() -> Unit,
- ) = renderPage(title = title, description, jwtPayload = jwtPayload) {
+ ) = renderPage(title = title, description, loggedInUser = loggedInUser) {
div("centered container mx-auto flex justify-center items-center") {
div("w-full md:w-1/2 lg:w-1/3 m-4") {
div("p-8 mb-6") {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/View.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/View.kt
similarity index 83%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/View.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/View.kt
index 998e19f..959cd42 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/View.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/View.kt
@@ -1,19 +1,16 @@
-package be.simplenotes.app.views
+package be.simplenotes.views
-import be.simplenotes.app.utils.StaticFileResolver
-import be.simplenotes.app.views.components.navbar
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
+import be.simplenotes.views.components.navbar
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
-abstract class View(staticFileResolver: StaticFileResolver) {
-
- private val styles = staticFileResolver.resolve("styles.css")!!
+abstract class View(private val styles: String) {
fun renderPage(
title: String,
description: String? = null,
- jwtPayload: JwtPayload?,
+ loggedInUser: LoggedInUser?,
scripts: List = emptyList(),
body: MAIN.() -> Unit = {},
) = buildString {
@@ -37,7 +34,7 @@ abstract class View(staticFileResolver: StaticFileResolver) {
}
}
body("bg-gray-900 text-white") {
- navbar(jwtPayload)
+ navbar(loggedInUser)
main { body() }
}
}
diff --git a/simplenotes-views/src/main/kotlin/be/simplenotes/views/ViewModule.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/ViewModule.kt
new file mode 100644
index 0000000..27699a4
--- /dev/null
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/ViewModule.kt
@@ -0,0 +1,12 @@
+package be.simplenotes.views
+
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+
+val viewModule = module {
+ single { ErrorView(get(named("styles"))) }
+ single { UserView(get(named("styles"))) }
+ single { BaseView(get(named("styles"))) }
+ single { SettingView(get(named("styles"))) }
+ single { NoteView(get(named("styles"))) }
+}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Alerts.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Alerts.kt
similarity index 73%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Alerts.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Alerts.kt
index 7fcb929..cac8691 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Alerts.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Alerts.kt
@@ -1,8 +1,8 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
import kotlinx.html.*
-fun FlowContent.alert(type: Alert, title: String, details: String? = null, multiline: Boolean = false) {
+internal fun FlowContent.alert(type: Alert, title: String, details: String? = null, multiline: Boolean = false) {
val colors = when (type) {
Alert.Success -> "bg-green-500 border border-green-400 text-gray-800"
Alert.Warning -> "bg-red-500 border border-red-400 text-red-200"
@@ -17,6 +17,6 @@ fun FlowContent.alert(type: Alert, title: String, details: String? = null, multi
}
}
-enum class Alert {
+internal enum class Alert {
Success, Warning
}
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/DeletedNoteTable.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/DeletedNoteTable.kt
similarity index 88%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/DeletedNoteTable.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/DeletedNoteTable.kt
index ee00851..d8ae6b2 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/DeletedNoteTable.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/DeletedNoteTable.kt
@@ -1,13 +1,13 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
-import be.simplenotes.app.utils.toTimeAgo
import be.simplenotes.types.PersistedNoteMetadata
+import be.simplenotes.views.utils.toTimeAgo
import kotlinx.html.*
import kotlinx.html.ButtonType.submit
import kotlinx.html.FormMethod.post
import kotlinx.html.ThScope.col
-fun FlowContent.deletedNoteTable(notes: List) = div("overflow-x-auto") {
+internal fun FlowContent.deletedNoteTable(notes: List) = div("overflow-x-auto") {
table {
id = "notes"
thead {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Forms.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Forms.kt
similarity index 88%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Forms.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Forms.kt
index 38faeec..92d6df5 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Forms.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Forms.kt
@@ -1,9 +1,9 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
import kotlinx.html.*
import kotlinx.html.ButtonType.submit
-fun FlowContent.input(
+internal fun FlowContent.input(
type: InputType = InputType.text,
placeholder: String,
id: String,
@@ -26,7 +26,7 @@ fun FlowContent.input(
}
}
-fun FlowContent.submitButton(text: String) {
+internal fun FlowContent.submitButton(text: String) {
div("flex items-center mt-6") {
button(
type = submit,
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Navbar.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Navbar.kt
similarity index 83%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Navbar.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Navbar.kt
index 864cf92..585a839 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/Navbar.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/Navbar.kt
@@ -1,9 +1,9 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
-import be.simplenotes.domain.security.JwtPayload
+import be.simplenotes.types.LoggedInUser
import kotlinx.html.*
-fun BODY.navbar(jwtPayload: JwtPayload?) {
+internal fun BODY.navbar(loggedInUser: LoggedInUser?) {
nav {
id = "navbar"
a("/") {
@@ -12,7 +12,7 @@ fun BODY.navbar(jwtPayload: JwtPayload?) {
}
ul("space-x-2") {
id = "navigation"
- if (jwtPayload != null) {
+ if (loggedInUser != null) {
val links = listOf(
"/notes" to "Notes",
"/settings" to "Settings",
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteListHeader.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteListHeader.kt
similarity index 88%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteListHeader.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteListHeader.kt
index 25b9a41..682f054 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteListHeader.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteListHeader.kt
@@ -1,10 +1,10 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
import kotlinx.html.*
import kotlinx.html.ButtonType.submit
import kotlinx.html.FormMethod.post
-fun DIV.noteListHeader(numberOfDeletedNotes: Int, query: String = "") {
+internal fun DIV.noteListHeader(numberOfDeletedNotes: Int, query: String = "") {
div("flex justify-between mb-4") {
h1("text-2xl underline") { +"Notes" }
span {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteTable.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteTable.kt
similarity index 85%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteTable.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteTable.kt
index a44bb62..5915287 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/views/components/NoteTable.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/components/NoteTable.kt
@@ -1,11 +1,11 @@
-package be.simplenotes.app.views.components
+package be.simplenotes.views.components
-import be.simplenotes.app.utils.toTimeAgo
import be.simplenotes.types.PersistedNoteMetadata
+import be.simplenotes.views.utils.toTimeAgo
import kotlinx.html.*
import kotlinx.html.ThScope.col
-fun FlowContent.noteTable(notes: List) = div("overflow-x-auto") {
+internal fun FlowContent.noteTable(notes: List) = div("overflow-x-auto") {
table {
id = "notes"
thead {
diff --git a/simplenotes-app/src/main/kotlin/be/simplenotes/app/extensions/KotlinxHtmlExtensions.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/extensions/KotlinxHtmlExtensions.kt
similarity index 55%
rename from simplenotes-app/src/main/kotlin/be/simplenotes/app/extensions/KotlinxHtmlExtensions.kt
rename to simplenotes-views/src/main/kotlin/be/simplenotes/views/extensions/KotlinxHtmlExtensions.kt
index ffc5678..0212341 100644
--- a/simplenotes-app/src/main/kotlin/be/simplenotes/app/extensions/KotlinxHtmlExtensions.kt
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/extensions/KotlinxHtmlExtensions.kt
@@ -1,8 +1,8 @@
-package be.simplenotes.app.extensions
+package be.simplenotes.views.extensions
import kotlinx.html.*
-class SUMMARY(consumer: TagConsumer<*>) :
+internal class SUMMARY(consumer: TagConsumer<*>) :
HTMLTag(
"summary", consumer, emptyMap(),
inlineTag = true,
@@ -10,6 +10,6 @@ class SUMMARY(consumer: TagConsumer<*>) :
),
HtmlInlineTag
-fun DETAILS.summary(block: SUMMARY.() -> Unit = {}) {
+internal fun DETAILS.summary(block: SUMMARY.() -> Unit = {}) {
SUMMARY(consumer).visit(block)
}
diff --git a/simplenotes-views/src/main/kotlin/be/simplenotes/views/utils/PrettyDate.kt b/simplenotes-views/src/main/kotlin/be/simplenotes/views/utils/PrettyDate.kt
new file mode 100644
index 0000000..40fdbf3
--- /dev/null
+++ b/simplenotes-views/src/main/kotlin/be/simplenotes/views/utils/PrettyDate.kt
@@ -0,0 +1,10 @@
+package be.simplenotes.views.utils
+
+import org.ocpsoft.prettytime.PrettyTime
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.util.*
+
+private val prettyTime = PrettyTime()
+
+internal fun LocalDateTime.toTimeAgo(): String = prettyTime.format(Date.from(atZone(ZoneId.systemDefault()).toInstant()))