Compare commits

...

2 Commits

Author SHA1 Message Date
ead1932d48 Fix some css 2020-10-23 07:16:07 +02:00
4a7dcec363 Use Json lenses 2020-10-23 06:22:44 +02:00
7 changed files with 75 additions and 66 deletions

View File

@ -1,6 +1,6 @@
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.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata import be.simplenotes.domain.model.PersistedNoteMetadata
@ -8,48 +8,39 @@ 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

View File

@ -1,25 +1,25 @@
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.domain.usecases.UserService import be.simplenotes.domain.usecases.UserService
import be.simplenotes.domain.usecases.users.login.LoginForm import be.simplenotes.domain.usecases.users.login.LoginForm
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
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 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) { 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): Response { fun login(request: Request) = userService
val form = json.decodeFromString(LoginForm.serializer(), request.bodyString()) .login(loginFormLens(request))
val result = userService.login(form) .fold(
return result.fold({ { Response(BAD_REQUEST) },
Response(Status.BAD_REQUEST) { tokenLens(Token(it), Response(OK)) }
}, { )
Response(Status.OK).json(json.encodeToString(Token.serializer(), Token(it)))
})
}
} }
@Serializable @Serializable

View File

@ -1,17 +1,35 @@
package be.simplenotes.app.extensions 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.Request
import org.http4k.core.Response import org.http4k.core.Response
import org.http4k.core.Status.Companion.FOUND import org.http4k.core.Status.Companion.FOUND
import org.http4k.core.Status.Companion.MOVED_PERMANENTLY import org.http4k.core.Status.Companion.MOVED_PERMANENTLY
import org.http4k.lens.*
fun Response.html(html: String) = body(html) fun Response.html(html: String) = body(html)
.header("Content-Type", "text/html; charset=utf-8") .header("Content-Type", "text/html; charset=utf-8")
.header("Cache-Control", "no-cache") .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) = fun Response.Companion.redirect(url: String, permanent: Boolean = false) =
Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url) Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url)
fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false 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) }
)

View File

@ -13,7 +13,7 @@ class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
) { ) {
section("text-center my-2 p-2") { section("text-center my-2 p-2") {
h1("text-5xl casual") { h1("text-5xl casual") {
span("text-teal-300") { +"Simplenotes " } span("text-teal-300") { +"SimpleNotes " }
+"- access your notes anywhere" +"- access your notes anywhere"
} }
} }

View File

@ -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") {
@ -169,8 +168,20 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
span("flex space-x-2 justify-end mb-4") { span("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") {
button(
type = ButtonType.submit,
name = if (note.public) "private" else "public",
classes = "btn btn-teal"
) {
if (note.public)
+"Private ?"
else
+"Public ?"
}
}
form(method = FormMethod.post, classes = "inline") { form(method = FormMethod.post, classes = "inline") {
button( button(
type = ButtonType.submit, type = ButtonType.submit,
@ -180,22 +191,4 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
} }
} }
} }
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 ?"
}
}
}
}
} }

View File

@ -26,22 +26,23 @@ class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResol
} }
} }
section("m-4 p-4 bg-gray-800 rounded flex justify-around") { section("m-4 p-2 bg-gray-800 rounded flex flex-wrap justify-around items-end") {
form(method = FormMethod.post, action = "/export") { form(classes = "m-2", method = FormMethod.post, action = "/export") {
button(name = "display", button(name = "display",
classes = "inline btn btn-teal block", classes = "inline btn btn-teal block",
type = submit) { +"Display my data" } type = submit) { +"Display my data" }
} }
form(method = FormMethod.post, action = "/export") { form(classes = "m-2", method = FormMethod.post, action = "/export") {
listOf("json", "zip").forEach { format -> div {
div { listOf("json", "zip").forEach { format ->
radioInput(name = "format") { radioInput(name = "format") {
id = format id = format
attributes["value"] = format attributes["value"] = format
if(format == "json") attributes["checked"] = "" if (format == "json") attributes["checked"] = ""
else attributes["class"] = "ml-4"
} }
label(classes = "ml-2") { label(classes = "ml-2") {
attributes["for"] = format attributes["for"] = format

View File

@ -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") {