Skip to content

Commit

Permalink
cleanup datetime types; added received timestamp for events.
Browse files Browse the repository at this point in the history
  • Loading branch information
jvorhauer committed Mar 1, 2024
1 parent af82cb6 commit a3f11ca
Show file tree
Hide file tree
Showing 16 changed files with 119 additions and 51 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Status

[![build](https://github.com/jvorhauer/konomas/actions/workflows/gradle.yml/badge.svg)](https://github.com/jvorhauer/noviblog/actions/workflows/gradle.yml)
[![coverage](https://codecov.io/gh/jvorhauer/noviblog/branch/main/graph/badge.svg?token=Nn5OmNCOEY)](https://codecov.io/gh/jvorhauer/noviblog)
[![coverage](https://codecov.io/gh/jvorhauer/konomas/branch/main/graph/badge.svg?token=Nn5OmNCOEY)](https://codecov.io/gh/jvorhauer/noviblog)

An Event Sourced version of the backend for the FrontEnd solution.

Expand Down
7 changes: 5 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ repositories {
maven {
url = uri("https://oss.sonatype.org/content/repositories/releases")
}
maven {
url = uri("https://jitpack.io")
}
}

dependencies {
Expand Down Expand Up @@ -91,8 +94,8 @@ tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "21"
languageVersion = "1.9"
allWarningsAsErrors = true
languageVersion = "2.0"
allWarningsAsErrors = false
}
}
tasks.withType<JavaCompile> {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/blog/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import blog.read.Reader
import blog.read.info
import blog.write.Processor

const val pid: String = "28"
const val pid: String = "29"

object Main {
private val kfg: Konfig = ConfigFactory.load("application.conf").extract("konomas")
Expand Down
40 changes: 26 additions & 14 deletions src/main/kotlin/blog/model/Note.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,31 @@ data class DeleteNote(val id: String, val rt: ActorRef<StatusReply<Done>>): Comm
val toEvent get() = NoteDeleted(id)
}

data class NoteCreated(val id: String, val user: String, val title: String, val body: String) : Event {

data class NoteCreated(
val id: String,
val user: String,
val title: String,
val body: String,
override val received: ZonedDateTime = znow
) : Event {
val toEntity get() = Note(id, user, title, title.slug, body)
val toResponse get() = this.toEntity.toResponse()
val toResponse get() = this.toEntity.toResponse
}

data class NoteUpdated(val id: String, val user: String, val title: String?, val body: String?): Event {
val timestamp: ZonedDateTime get() = TSID.from(id).instant.atZone(CET)
}
data class NoteUpdated(
val id: String,
val user: String,
val title: String?,
val body: String?,
override val received: ZonedDateTime = znow
): Event

data class NoteDeleted(val id: String): Event
data class NoteDeleted(
val id: String,
override val received: ZonedDateTime = znow
): Event

data class NoteDelta(val updated: ZonedDateTime, val what: String)

data class Note(
override val id: String,
Expand All @@ -63,19 +76,19 @@ data class Note(
val slug: String,
val body: String,
val created: ZonedDateTime = TSID.from(id).instant.atZone(CET),
val updated: ZonedDateTime = znow,
val events: List<NoteUpdated> = listOf()
val updated: ZonedDateTime = znow
): Entity {
constructor(id: String, user: String, title: String, body: String): this(id, user, title, title.slug, body)
fun update(nu: NoteUpdated): Note = this.copy(
title = nu.title ?: this.title, slug = nu.title?.slug ?: this.slug, body = nu.body ?: this.body, updated = znow, events = this.events + nu
title = nu.title ?: this.title, slug = nu.title?.slug ?: this.slug, body = nu.body ?: this.body, updated = nu.received
)
fun toResponse() = NoteResponse(id, user, DTF.format(created), DTF.format(updated), title, slug, body, events.map { NoteDelta(it.timestamp, "???") })
val toResponse get() = NoteResponse(id, user, created.fmt, updated.fmt, title, slug, body)

override fun equals(other: Any?): Boolean = equals(this, other)
override fun hashCode(): Int = id.hashCode()
}


data class NoteResponse(
val id: String,
val user: String,
Expand All @@ -84,7 +97,6 @@ data class NoteResponse(
val title: String,
val slug: String,
val body: String,
val deltas: List<NoteDelta>
)

fun Route.notesRoute(processor: ActorRef<Command>, reader: Reader, scheduler: Scheduler, kfg: Konfig) =
Expand All @@ -105,12 +117,12 @@ fun Route.notesRoute(processor: ActorRef<Command>, reader: Reader, scheduler: Sc
get {
val rows = call.request.queryParameters["rows"]?.toInt() ?: 10
val start = call.request.queryParameters["start"]?.toInt() ?: 0
call.respond(reader.allNotes(rows, start).map { it.toResponse() })
call.respond(reader.allNotes(rows, start).map { it.toResponse })
}
get("{id?}") {
val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.NotFound, "note id not specified")
val note = reader.findNote(id) ?: return@get call.respond(HttpStatusCode.NotFound, "note not found for $id")
call.respond(note.toResponse())
call.respond(note.toResponse)
}
put {
val unr = call.receive<UpdateNoteRequest>()
Expand Down
7 changes: 6 additions & 1 deletion src/main/kotlin/blog/model/Tag.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package blog.model

import java.time.ZonedDateTime
import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.javadsl.AskPattern.ask
Expand Down Expand Up @@ -30,7 +31,11 @@ data class CreateTag(val id: String, val label: String, val replyTo: ActorRef<St
val toEvent get() = TagCreated(id, label)
}

data class TagCreated(val id: String, val label: String) : Event {
data class TagCreated(
val id: String,
val label: String,
override val received: ZonedDateTime = znow
) : Event {
val toEntity get() = Tag(id, label)
}

Expand Down
25 changes: 15 additions & 10 deletions src/main/kotlin/blog/model/Task.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ data class Task(
val title: String,
val slug: String,
val body: String,
val due: LocalDateTime,
val due: ZonedDateTime,
val status: TaskStatus = TaskStatus.TODO,
val private: Boolean = true,
val created: ZonedDateTime = TSID.from(id).instant.atZone(CET),
val updated: ZonedDateTime = znow
val updated: ZonedDateTime = TSID.from(id).instant.atZone(CET)
) : Entity {
fun update(tu: TaskUpdated): Task = this.copy(
title = tu.title ?: this.title,
slug = tu.title?.slug ?: this.slug,
body = tu.body ?: this.body,
due = tu.due ?: this.due,
status = tu.status ?: this.status,
updated = znow
updated = tu.received
)

val toResponse get() = TaskResponse(id, created.fmt, updated.fmt, user, title, body, due.fmt, status.name)
Expand All @@ -49,19 +49,19 @@ data class Task(
}

data class CreateTaskRequest(val title: String, val body: String, val due: LocalDateTime): Request {
fun toCommand(user: String, replyTo: ActorRef<StatusReply<TaskResponse>>) = CreateTask(user, title.encode, body.encode, due, replyTo)
fun toCommand(user: String, replyTo: ActorRef<StatusReply<TaskResponse>>) = CreateTask(user, title.encode, body.encode, due.atZone(CET), 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<StatusReply<TaskResponse>>) = UpdateTask(user, id, title.mencode, body.mencode, due, status, replyTo)
fun toCommand(user: String, replyTo: ActorRef<StatusReply<TaskResponse>>) = UpdateTask(user, id, title.mencode, body.mencode, due?.atZone(CET), status, replyTo)
}


data class CreateTask(
val user: String,
val title: String,
val body: String,
val due: LocalDateTime,
val due: ZonedDateTime,
val replyTo: ActorRef<StatusReply<TaskResponse>>,
val id: String = nextId
) : Command {
Expand All @@ -73,7 +73,7 @@ data class UpdateTask(
val id: String,
val title: String?,
val body: String?,
val due: LocalDateTime?,
val due: ZonedDateTime?,
val status: TaskStatus?,
val replyTo: ActorRef<StatusReply<TaskResponse>>
) : Command {
Expand All @@ -90,7 +90,8 @@ data class TaskCreated(
val user: String,
val title: String,
val body: String,
val due: LocalDateTime
val due: ZonedDateTime,
override val received: ZonedDateTime = znow
) : Event {
val toEntity get() = Task(id, user, title, title.slug, body, due)
val toResponse get() = toEntity.toResponse
Expand All @@ -101,11 +102,15 @@ data class TaskUpdated(
val id: String,
val title: String?,
val body: String?,
val due: LocalDateTime?,
val due: ZonedDateTime?,
val status: TaskStatus?,
override val received: ZonedDateTime = znow
) : Event

data class TaskDeleted(val id: String): Event
data class TaskDeleted(
val id: String,
override val received: ZonedDateTime = znow
): Event


data class TaskResponse(
Expand Down
11 changes: 8 additions & 3 deletions src/main/kotlin/blog/model/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,22 @@ data class UserCreated(
val email: String,
val name: String,
val password: String,
override val received: ZonedDateTime = znow
) : Event {
val toEntity: User get() = User(id, email, name, password)
}

data class UserUpdated(
val id: String,
val name: String?,
val password: String?
val password: String?,
override val received: ZonedDateTime = znow
): Event

data class UserDeleted(val id: String) : Event
data class UserDeleted(
val id: String,
override val received: ZonedDateTime = znow
) : Event

// Entitites

Expand Down Expand Up @@ -157,7 +162,7 @@ fun Route.usersRoute(processor: ActorRef<Command>, reader: Reader, scheduler: Sc
}
get("/notes") {
val userId = userIdFromJWT(call) ?: return@get call.respond(Unauthorized, "Unauthorized")
call.respond(reader.findNotesForUser(userId).sortedBy { it.created }.map { it.toResponse() })
call.respond(reader.findNotesForUser(userId).sortedBy { it.created }.map { it.toResponse })
}
put {
val userId = userIdFromJWT(call) ?: return@put call.respond(Unauthorized, "Unauthorized")
Expand Down
25 changes: 18 additions & 7 deletions src/main/kotlin/blog/model/model.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,35 @@ import io.hypersistence.tsid.TSID
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import blog.model.Constants.idNode
import blog.model.Constants.mdi
import blog.model.Constants.randm

interface Request : Serializable

interface Command : Serializable
interface Event : Serializable

interface Event : Serializable {
val received: ZonedDateTime
}

interface Entity : Serializable {
val id: String
}

interface Response : Serializable {
val id: String
}

object Constants {
val randm: SecureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN")
val idNode: Int = InetAddress.getLocalHost().address[3].toInt()and(0xFF)
val mdi: MessageDigest = MessageDigest.getInstance("SHA-256")
}

object Hasher {
private val md = MessageDigest.getInstance("SHA-256")
private fun toHex(ba: ByteArray) = ba.joinToString(separator = "") { String.format(Locale.US, "%02x", it) }
fun hash(s: String): String = toHex(md.digest(s.toByteArray(StandardCharsets.UTF_8)))
fun hash(s: String): String = toHex(mdi.digest(s.toByteArray(StandardCharsets.UTF_8)))
}
val String.hashed: String get() = Hasher.hash(this)
val String.gravatar: String get() = this.trim().lowercase().hashed
Expand All @@ -44,10 +58,7 @@ val inow: Instant get() = Instant.now()
val znow: ZonedDateTime get() = inow.atZone(CET)
val now: LocalDateTime get() = LocalDateTime.ofInstant(inow, CET)

private val idFactory = TSID.Factory.builder()
.withRandom(SecureRandom.getInstance("SHA1PRNG", "SUN"))
.withNodeBits(8)
.withNode(InetAddress.getLocalHost().address[3].toInt()and(0xFF)).build()
private val idFactory = TSID.Factory.builder().withRandom(randm).withNodeBits(8).withNode(idNode).build()
val nextTSID: TSID get() = idFactory.generate()
val nextId: String get() = nextTSID.toString()

Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/blog/read/Reader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Reader(
is TagCreated -> tags[e.id] = e.toEntity
else -> logger.warn("could not processEvent {}", e)
}
logger.info("processEvent: $e")
}

companion object {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/blog/write/Processor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe
Effect().none().thenReply(cmd.replyTo) { StatusReply.error("Note with id ${cmd.id} not found for user with id ${cmd.user}") }
} else {
cmd.toEvent.let {
Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findNote(it.id)?.toResponse()) }
Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findNote(it.id)?.toResponse) }
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/test/kotlin/blog/ApiTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ class ApiTests {
assertThat(response.status.value).isEqualTo(200)
val ur = response.body<UserResponse>()
assertThat(ur.name).isEqualTo("Anders")

response = client.get("http://localhost:8181/api/users/me") {
header("Authorization", "Bearer ${token.token}")
}
assertThat(response.status.value).isEqualTo(200)
val updatedUser: UserResponse = response.body<UserResponse>()
assertThat(updatedUser.name).isEqualTo("Anders")
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/test/kotlin/blog/model/NoteTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class NoteTests {
assertThat(nc.user).isEqualTo(userId)
assertThat(nc.title).isEqualTo("title")
assertThat(nc.body).isEqualTo("body")
assertThat(nc.received).isNotNull
assertThat(nc.received).isBefore(znow)

val note = nc.toEntity
assertThat(note.id).isNotNull()
Expand Down
3 changes: 1 addition & 2 deletions src/test/kotlin/blog/model/StateTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package blog.model

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.time.LocalDateTime

class StateTests {

Expand Down Expand Up @@ -75,7 +74,7 @@ class StateTests {
val state = State()
val user = User(nextId, "[email protected]", "Tester", pw)
val state2 = state.save(user)
val task = Task(nextId, user.id, "Test", "test", "Tasking, 1.. 2..", LocalDateTime.now().plusHours(1))
val task = Task(nextId, user.id, "Test", "test", "Tasking, 1.. 2..", znow.plusHours(1))
val state3 = state2.save(task)
assertThat(state3.taskCount()).isEqualTo(1)
assertThat(state3.findTask(task.id)).isNotNull
Expand Down
Loading

0 comments on commit a3f11ca

Please sign in to comment.