Compare commits
2 Commits
cb76a3253d
...
ead1932d48
| Author | SHA1 | Date | |
|---|---|---|---|
| ead1932d48 | |||
| 4a7dcec363 |
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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) }
|
||||||
|
)
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 ?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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") {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user