diff --git a/app/src/main/kotlin/api/ApiNoteController.kt b/app/src/main/kotlin/api/ApiNoteController.kt index 27f0808..ef300e2 100644 --- a/app/src/main/kotlin/api/ApiNoteController.kt +++ b/app/src/main/kotlin/api/ApiNoteController.kt @@ -1,6 +1,6 @@ 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.domain.model.PersistedNote import be.simplenotes.domain.model.PersistedNoteMetadata @@ -8,48 +8,39 @@ import be.simplenotes.domain.security.JwtPayload import be.simplenotes.domain.usecases.NoteService import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.ListSerializer 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.NOT_FOUND 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.* class ApiNoteController(private val noteService: NoteService, private val json: Json) { 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( - { - Response(BAD_REQUEST) - }, - { - Response(OK).json(json.encodeToString(UuidContent.serializer(), UuidContent(it.uuid))) - } + { 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 - val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes) - return Response(OK).json(json) + return persistedNotesMetadataLens(notes, Response(OK)) } - fun note(request: Request, jwtPayload: JwtPayload): Response { - val uuid = request.path("uuid")!! - - return noteService.find(jwtPayload.userId, UUID.fromString(uuid)) - ?.let { Response(OK).json(json.encodeToString(PersistedNote.serializer(), it)) } + fun note(request: Request, jwtPayload: JwtPayload): Response = + noteService.find(jwtPayload.userId, uuidLens(request)) + ?.let { persistedNoteLens(it, Response(OK)) } ?: Response(NOT_FOUND) - } fun update(request: Request, jwtPayload: JwtPayload): Response { - val uuid = UUID.fromString(request.path("uuid")!!) - val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content - return noteService.update(jwtPayload.userId, uuid, content).fold({ + val content = noteContentLens(request) + return noteService.update(jwtPayload.userId, uuidLens(request), content).fold({ Response(BAD_REQUEST) }, { 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 { - val query = json.decodeFromString(SearchContent.serializer(), request.bodyString()).query + val query = searchContentLens(request) val terms = parseSearchTerms(query) val notes = noteService.search(jwtPayload.userId, terms) - val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes) - return Response(OK).json(json) + return persistedNotesMetadataLens(notes, Response(OK)) } + private val uuidContentLens = json.auto().toLens() + private val noteContentLens = json.auto().map { it.content }.toLens() + private val searchContentLens = json.auto().map { it.query }.toLens() + private val persistedNotesMetadataLens = json.auto>().toLens() + private val persistedNoteLens = json.auto().toLens() + private val uuidLens = Path.uuid().of("uuid") + } @Serializable diff --git a/app/src/main/kotlin/api/ApiUserController.kt b/app/src/main/kotlin/api/ApiUserController.kt index bb1eb96..da9f1ac 100644 --- a/app/src/main/kotlin/api/ApiUserController.kt +++ b/app/src/main/kotlin/api/ApiUserController.kt @@ -1,25 +1,25 @@ 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.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 +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().toLens() + private val loginFormLens = json.auto().toLens() - 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))) - }) - } + fun login(request: Request) = userService + .login(loginFormLens(request)) + .fold( + { Response(BAD_REQUEST) }, + { tokenLens(Token(it), Response(OK)) } + ) } @Serializable diff --git a/app/src/main/kotlin/extensions/Http4kExtensions.kt b/app/src/main/kotlin/extensions/Http4kExtensions.kt index 1c251ae..cd2916e 100644 --- a/app/src/main/kotlin/extensions/Http4kExtensions.kt +++ b/app/src/main/kotlin/extensions/Http4kExtensions.kt @@ -1,17 +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.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 + +val bodyLens = httpBodyRoot( + listOf(Meta(true, "body", ParamMeta.ObjectParam, "body")), + ContentType.APPLICATION_JSON.withNoDirectives(), ContentNegotiation.StrictNoDirective +).map( + { it.payload.asString() }, + { Body(it) } +) + +inline fun Json.auto(): BiDiBodyLensSpec = bodyLens.map( + { decodeFromString(it) }, + { encodeToString(it) } +)