diff --git a/src/main/kotlin/blog/model/Note.kt b/src/main/kotlin/blog/model/Note.kt index ab3e908..4075f1e 100644 --- a/src/main/kotlin/blog/model/Note.kt +++ b/src/main/kotlin/blog/model/Note.kt @@ -19,11 +19,11 @@ import kotlinx.coroutines.future.await import blog.read.Reader data class CreateNoteRequest(val title: String, val body: String) { - fun toCommand(user: String, replyTo: ActorRef>) = CreateNote(user, title, body, replyTo) + fun toCommand(user: String, replyTo: ActorRef>) = CreateNote(user, Encode.forHtml(title), Encode.forHtml(body), replyTo) } data class UpdateNoteRequest(val id: String, val title: String?, val body: String?) { - fun toCommand(user: String, rt: ActorRef>) = UpdateNote(user, id, title, body, rt) + fun toCommand(user: String, rt: ActorRef>) = UpdateNote(user, id, title.encode(), body.encode(), rt) } data class CreateNote( @@ -33,7 +33,7 @@ data class CreateNote( val replyTo: ActorRef>, val id: String = nextId() ) : Command { - fun toEvent() = NoteCreated(id, user, Encode.forHtml(title), Encode.forHtml(body)) + fun toEvent() = NoteCreated(id, user, title, body) } data class UpdateNote(val user: String, val id: String, val title: String?, val body: String?, val replyTo: ActorRef>): Command { @@ -63,7 +63,7 @@ data class Note( ): Entity { constructor(id: String, user: String, title: String, body: String): this(id, user, title, slugify(title), body) fun update(nu: NoteUpdated): Note = this.copy(title = nu.title ?: this.title, slug = slugify(nu.title ?: this.title), body = nu.body ?: this.body) - fun toResponse() = NoteResponse(id, user, DTF.format(created), title, slug, body) + fun toResponse() = NoteResponse(id, user, DTF.format(created), title, slug, body.replace("\n", "
")) } data class NoteResponse( @@ -100,6 +100,16 @@ fun Route.notesRoute(processor: ActorRef, reader: Reader, scheduler: Sc val note = reader.findNote(id) ?: return@get call.respond(HttpStatusCode.NotFound, "note not found for $id") call.respond(note.toResponse()) } + put { + val unr = call.receive() + val userId = user(call) ?: return@put call.respond(HttpStatusCode.Unauthorized, "Unauthorized") + AskPattern.ask(processor, { rt -> unr.toCommand(userId, rt) }, timeout, scheduler).await().let { + when { + it.isSuccess -> call.respond(HttpStatusCode.OK, it.value) + else -> call.respond(HttpStatusCode.BadRequest, it.error.localizedMessage) + } + } + } delete("{id?}") { val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.NotFound, "note id not specified") reader.findNote(id) ?: return@delete call.respond(HttpStatusCode.NotFound, "note not found for $id") diff --git a/src/main/kotlin/blog/model/Task.kt b/src/main/kotlin/blog/model/Task.kt index 03de5f8..431cff7 100644 --- a/src/main/kotlin/blog/model/Task.kt +++ b/src/main/kotlin/blog/model/Task.kt @@ -8,6 +8,7 @@ import akka.actor.typed.ActorRef import akka.actor.typed.Scheduler import akka.actor.typed.javadsl.AskPattern.ask import akka.pattern.StatusReply +import org.owasp.encoder.Encode import io.hypersistence.tsid.TSID import io.ktor.http.* import io.ktor.server.application.* @@ -48,11 +49,11 @@ data class Task( } data class CreateTaskRequest(val title: String, val body: String, val due: LocalDateTime): Request { - fun toCommand(user: String, replyTo: ActorRef>) = CreateTask(user, title, body, due, replyTo) + fun toCommand(user: String, replyTo: ActorRef>) = CreateTask(user, Encode.forHtml(title), Encode.forHtml(body), due, replyTo) } data class UpdateTaskRequest(val id: String, val title: String?, val body: String?, val due: LocalDateTime?, val status: TaskStatus?): Request { - fun toCommand(user: String, replyTo: ActorRef>) = UpdateTask(user, id, title, body, due, status, replyTo) + fun toCommand(user: String, replyTo: ActorRef>) = UpdateTask(user, id, title.encode(), body.encode(), due, status, replyTo) } diff --git a/src/main/kotlin/blog/model/User.kt b/src/main/kotlin/blog/model/User.kt index 6ffd362..20de9b7 100644 --- a/src/main/kotlin/blog/model/User.kt +++ b/src/main/kotlin/blog/model/User.kt @@ -131,7 +131,7 @@ fun Route.usersRoute(processor: ActorRef, reader: Reader, scheduler: Sc } get("/notes") { val userId = user(call) ?: return@get call.respond(Unauthorized, "Unauthorized") - call.respond(reader.findNotesForUser(userId).map { it.toResponse() }) + call.respond(reader.findNotesForUser(userId).sortedBy { it.created }.map { it.toResponse() }) } } get("{id?}") { diff --git a/src/main/kotlin/blog/model/model.kt b/src/main/kotlin/blog/model/model.kt index cb1eb61..d173446 100644 --- a/src/main/kotlin/blog/model/model.kt +++ b/src/main/kotlin/blog/model/model.kt @@ -9,6 +9,7 @@ import java.time.Duration import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.Locale +import org.owasp.encoder.Encode import io.hypersistence.tsid.TSID import io.ktor.server.application.* import io.ktor.server.auth.* @@ -43,6 +44,7 @@ fun nextId(): String = idFactory.generate().toString() fun slugify(s: String): String = s.trim().replace(" ", " ").lowercase().replace("[^ a-z0-9]".toRegex(), "").replace(' ', '-') fun String.hashed() = Hasher.hash(this) +fun String?.encode(): String? = if (this == null) null else Encode.forHtml(this) data class Counts( val users: Int, diff --git a/src/main/kotlin/blog/read/Reader.kt b/src/main/kotlin/blog/read/Reader.kt index 025a0f4..ccc7200 100644 --- a/src/main/kotlin/blog/read/Reader.kt +++ b/src/main/kotlin/blog/read/Reader.kt @@ -58,7 +58,6 @@ class Reader( fun counts(): Counts = Counts(users.size, notes.size, tasks.size) fun processEvent(e: Event) { - logger.info("processEvent: {}", e) when (e) { is UserCreated -> users[e.id] = e.toEntity() is UserDeleted -> users.remove(e.id) diff --git a/src/test/kotlin/blog/model/NoteTests.kt b/src/test/kotlin/blog/model/NoteTests.kt index c992ac2..c1d2f3b 100644 --- a/src/test/kotlin/blog/model/NoteTests.kt +++ b/src/test/kotlin/blog/model/NoteTests.kt @@ -6,6 +6,7 @@ import akka.pattern.StatusReply import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.util.UUID +import org.owasp.encoder.Encode class NoteTests { @@ -65,6 +66,11 @@ class NoteTests { assertThat(updated.user).isEqualTo(userId) assertThat(updated.title).isEqualTo(run.title) assertThat(updated.body).isEqualTo(run.body) + + var str = Encode.forHtml("") + assertThat(str).isEmpty() + str = Encode.forHtml(null) + assertThat(str).isNotNull(); } @Test diff --git a/src/test/requests/users-v2.http b/src/test/requests/users-v2.http index cb6cc5e..3bcab4c 100644 --- a/src/test/requests/users-v2.http +++ b/src/test/requests/users-v2.http @@ -37,8 +37,8 @@ Content-Type: application/json Authorization: Bearer {{authorized}} { - "title": "My First Note", - "body": "Note taking in its simplest form :-)" + "title": "My Second Note", + "body": "More notes is just better!" } ### get-all-notes