From 757d1081bd27fc1644a52037da16db00d67f0699 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 27 Aug 2023 16:49:53 +0200 Subject: [PATCH] feat: with sled db Signed-off-by: kjuulh --- Cargo.lock | 133 ++++++++++- Cargo.toml | 3 + crates/churn-capnp/Cargo.toml | 20 ++ crates/churn-capnp/build.rs | 10 + crates/churn-capnp/schemas/models.capnp | 7 + crates/churn-capnp/src/lib.rs | 59 +++++ crates/churn-capnp/src/models_capnp.rs | 288 ++++++++++++++++++++++++ crates/churn-domain/src/lib.rs | 17 ++ crates/churn-server/Cargo.toml | 4 + crates/churn-server/src/db.rs | 79 +++++++ crates/churn-server/src/event.rs | 61 +++-- crates/churn-server/src/main.rs | 32 ++- 12 files changed, 682 insertions(+), 31 deletions(-) create mode 100644 crates/churn-capnp/Cargo.toml create mode 100644 crates/churn-capnp/build.rs create mode 100644 crates/churn-capnp/schemas/models.capnp create mode 100644 crates/churn-capnp/src/lib.rs create mode 100644 crates/churn-capnp/src/models_capnp.rs create mode 100644 crates/churn-server/src/db.rs diff --git a/Cargo.lock b/Cargo.lock index 46b4170..7a8fb1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,6 +217,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "capnp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e65021d89250bbfe7c2791789ced2c4bdc21b0e8bb59c64f3fd6145a5fd678" + +[[package]] +name = "capnpc" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbc3763fb3e6635188e9cc51ee11a26f8777c553ca377430818dbebaaf6042b" +dependencies = [ + "capnp", +] + [[package]] name = "cc" version = "1.0.79" @@ -262,6 +277,17 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "churn-capnp" +version = "0.1.0" +dependencies = [ + "anyhow", + "capnp", + "capnpc", + "churn-domain", + "uuid", +] + [[package]] name = "churn-domain" version = "0.1.0" @@ -283,12 +309,15 @@ name = "churn-server" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "axum", + "churn-capnp", "churn-domain", "clap", "dotenv", "serde", "serde_json", + "sled", "tokio", "tracing", "tracing-subscriber", @@ -400,6 +429,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -671,6 +722,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.28" @@ -760,6 +821,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1021,6 +1091,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1117,6 +1196,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1246,6 +1334,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1253,7 +1352,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1643,6 +1756,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -1789,7 +1918,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2 0.5.3", diff --git a/Cargo.toml b/Cargo.toml index fc4eae6..81ad417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ churn = { path = "crates/churn" } churn-agent = { path = "crates/churn-agent" } churn-server = { path = "crates/churn-server" } churn-domain = { path = "crates/churn-domain", version = "0.1.0" } +churn-capnp = { path = "crates/churn-capnp", version = "0.1.0" } anyhow = { version = "1.0.71" } tokio = { version = "1", features = ["full"] } @@ -30,3 +31,5 @@ serde = {version = "1", features = ["derive"]} serde_json = "1" reqwest = {version = "0.11.20", features = ["json"]} uuid = {version = "1.4.1", features = ["v4", "serde"]} + +sled = "0.34.7" \ No newline at end of file diff --git a/crates/churn-capnp/Cargo.toml b/crates/churn-capnp/Cargo.toml new file mode 100644 index 0000000..1193f12 --- /dev/null +++ b/crates/churn-capnp/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "churn-capnp" +repository.workspace = true +description.workspace = true +readme.workspace = true +license-file.workspace = true +authors.workspace = true +version.workspace = true +edition.workspace = true +publish.workspace = true + +[dependencies] +churn-domain.workspace = true +uuid.workspace = true +anyhow.workspace = true + +capnp = "0.17.2" + +[build-dependencies] +capnpc = "0.17.2" diff --git a/crates/churn-capnp/build.rs b/crates/churn-capnp/build.rs new file mode 100644 index 0000000..42bedc4 --- /dev/null +++ b/crates/churn-capnp/build.rs @@ -0,0 +1,10 @@ +extern crate capnpc; + +fn main() { + capnpc::CompilerCommand::new() + .output_path("src/") + .src_prefix("schemas/") + .file("schemas/models.capnp") + .run() + .unwrap(); +} diff --git a/crates/churn-capnp/schemas/models.capnp b/crates/churn-capnp/schemas/models.capnp new file mode 100644 index 0000000..1c9c311 --- /dev/null +++ b/crates/churn-capnp/schemas/models.capnp @@ -0,0 +1,7 @@ +@0xf23adf24ffd8aca4; + +struct LogEvent { + id @0 :Text; + author @1 :Text; + content @2 :Text; +} diff --git a/crates/churn-capnp/src/lib.rs b/crates/churn-capnp/src/lib.rs new file mode 100644 index 0000000..16206b2 --- /dev/null +++ b/crates/churn-capnp/src/lib.rs @@ -0,0 +1,59 @@ +use capnp::message::{Builder, HeapAllocator}; +use capnp::message::{ReaderOptions, TypedReader}; +use capnp::serialize::{self, OwnedSegments}; + +use capnp::traits::{FromPointerReader, Owned}; +use churn_domain::LogEvent; + +mod models_capnp; + +pub trait CapnpPackExt { + type Return; + + fn serialize_capnp(&self) -> String; + fn deserialize_capnp(content: &str) -> anyhow::Result; + + fn capnp_to_string(builder: &Builder) -> String { + let msg = serialize::write_message_to_words(builder); + std::str::from_utf8(msg.as_slice()) + .expect("to be able to parse capnp as utf8") + .to_string() + } + + fn string_to_capnp<'a, S>(content: &'a str) -> TypedReader + where + S: Owned, + { + let log_event = serialize::read_message(content.as_bytes(), ReaderOptions::new()).unwrap(); + + let log_event = log_event.into_typed::(); + + log_event + } +} + +impl CapnpPackExt for LogEvent { + type Return = Self; + + fn serialize_capnp(&self) -> String { + let mut builder = Builder::new_default(); + + let mut log_event = builder.init_root::(); + log_event.set_id(&self.id.to_string()); + log_event.set_author(&self.author); + log_event.set_content(&self.content); + + Self::capnp_to_string(&builder) + } + + fn deserialize_capnp(content: &str) -> anyhow::Result { + let log_event = Self::string_to_capnp::(content); + let log_event = log_event.get()?; + + Ok(Self { + id: uuid::Uuid::parse_str(log_event.get_id()?)?, + author: log_event.get_author()?.into(), + content: log_event.get_content()?.into(), + }) + } +} diff --git a/crates/churn-capnp/src/models_capnp.rs b/crates/churn-capnp/src/models_capnp.rs new file mode 100644 index 0000000..73e1e46 --- /dev/null +++ b/crates/churn-capnp/src/models_capnp.rs @@ -0,0 +1,288 @@ +// @generated by the capnpc-rust plugin to the Cap'n Proto schema compiler. +// DO NOT EDIT. +// source: models.capnp + + +pub mod log_event { + #[derive(Copy, Clone)] + pub struct Owned(()); + impl ::capnp::introspect::Introspect for Owned { fn introspect() -> ::capnp::introspect::Type { ::capnp::introspect::TypeVariant::Struct(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types, annotation_types: _private::get_annotation_types }).into() } } + impl ::capnp::traits::Owned for Owned { type Reader<'a> = Reader<'a>; type Builder<'a> = Builder<'a>; } + impl ::capnp::traits::OwnedStruct for Owned { type Reader<'a> = Reader<'a>; type Builder<'a> = Builder<'a>; } + impl ::capnp::traits::Pipelined for Owned { type Pipeline = Pipeline; } + + pub struct Reader<'a> { reader: ::capnp::private::layout::StructReader<'a> } + impl <'a,> ::core::marker::Copy for Reader<'a,> {} + impl <'a,> ::core::clone::Clone for Reader<'a,> { + fn clone(&self) -> Self { *self } + } + + impl <'a,> ::capnp::traits::HasTypeId for Reader<'a,> { + const TYPE_ID: u64 = _private::TYPE_ID; + } + impl <'a,> ::core::convert::From<::capnp::private::layout::StructReader<'a>> for Reader<'a,> { + fn from(reader: ::capnp::private::layout::StructReader<'a>) -> Self { + Self { reader, } + } + } + + impl <'a,> ::core::convert::From> for ::capnp::dynamic_value::Reader<'a> { + fn from(reader: Reader<'a,>) -> Self { + Self::Struct(::capnp::dynamic_struct::Reader::new(reader.reader, ::capnp::schema::StructSchema::new(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types::<>, annotation_types: _private::get_annotation_types::<>}))) + } + } + + impl <'a,> ::core::fmt::Debug for Reader<'a,> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::result::Result<(), ::core::fmt::Error> { + core::fmt::Debug::fmt(&::core::convert::Into::<::capnp::dynamic_value::Reader<'_>>::into(*self), f) + } + } + + impl <'a,> ::capnp::traits::FromPointerReader<'a> for Reader<'a,> { + fn get_from_pointer(reader: &::capnp::private::layout::PointerReader<'a>, default: ::core::option::Option<&'a [::capnp::Word]>) -> ::capnp::Result { + ::core::result::Result::Ok(reader.get_struct(default)?.into()) + } + } + + impl <'a,> ::capnp::traits::IntoInternalStructReader<'a> for Reader<'a,> { + fn into_internal_struct_reader(self) -> ::capnp::private::layout::StructReader<'a> { + self.reader + } + } + + impl <'a,> ::capnp::traits::Imbue<'a> for Reader<'a,> { + fn imbue(&mut self, cap_table: &'a ::capnp::private::layout::CapTable) { + self.reader.imbue(::capnp::private::layout::CapTableReader::Plain(cap_table)) + } + } + + impl <'a,> Reader<'a,> { + pub fn reborrow(&self) -> Reader<'_,> { + Self { .. *self } + } + + pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> { + self.reader.total_size() + } + #[inline] + pub fn get_id(self) -> ::capnp::Result<::capnp::text::Reader<'a>> { + ::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(0), ::core::option::Option::None) + } + #[inline] + pub fn has_id(&self) -> bool { + !self.reader.get_pointer_field(0).is_null() + } + #[inline] + pub fn get_author(self) -> ::capnp::Result<::capnp::text::Reader<'a>> { + ::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(1), ::core::option::Option::None) + } + #[inline] + pub fn has_author(&self) -> bool { + !self.reader.get_pointer_field(1).is_null() + } + #[inline] + pub fn get_content(self) -> ::capnp::Result<::capnp::text::Reader<'a>> { + ::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(2), ::core::option::Option::None) + } + #[inline] + pub fn has_content(&self) -> bool { + !self.reader.get_pointer_field(2).is_null() + } + } + + pub struct Builder<'a> { builder: ::capnp::private::layout::StructBuilder<'a> } + impl <'a,> ::capnp::traits::HasStructSize for Builder<'a,> { + const STRUCT_SIZE: ::capnp::private::layout::StructSize = ::capnp::private::layout::StructSize { data: 0, pointers: 3 }; + } + impl <'a,> ::capnp::traits::HasTypeId for Builder<'a,> { + const TYPE_ID: u64 = _private::TYPE_ID; + } + impl <'a,> ::core::convert::From<::capnp::private::layout::StructBuilder<'a>> for Builder<'a,> { + fn from(builder: ::capnp::private::layout::StructBuilder<'a>) -> Self { + Self { builder, } + } + } + + impl <'a,> ::core::convert::From> for ::capnp::dynamic_value::Builder<'a> { + fn from(builder: Builder<'a,>) -> Self { + Self::Struct(::capnp::dynamic_struct::Builder::new(builder.builder, ::capnp::schema::StructSchema::new(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types::<>, annotation_types: _private::get_annotation_types::<>}))) + } + } + + impl <'a,> ::capnp::traits::ImbueMut<'a> for Builder<'a,> { + fn imbue_mut(&mut self, cap_table: &'a mut ::capnp::private::layout::CapTable) { + self.builder.imbue(::capnp::private::layout::CapTableBuilder::Plain(cap_table)) + } + } + + impl <'a,> ::capnp::traits::FromPointerBuilder<'a> for Builder<'a,> { + fn init_pointer(builder: ::capnp::private::layout::PointerBuilder<'a>, _size: u32) -> Self { + builder.init_struct(::STRUCT_SIZE).into() + } + fn get_from_pointer(builder: ::capnp::private::layout::PointerBuilder<'a>, default: ::core::option::Option<&'a [::capnp::Word]>) -> ::capnp::Result { + ::core::result::Result::Ok(builder.get_struct(::STRUCT_SIZE, default)?.into()) + } + } + + impl <'a,> ::capnp::traits::SetPointerBuilder for Reader<'a,> { + fn set_pointer_builder(mut pointer: ::capnp::private::layout::PointerBuilder<'_>, value: Self, canonicalize: bool) -> ::capnp::Result<()> { pointer.set_struct(&value.reader, canonicalize) } + } + + impl <'a,> Builder<'a,> { + pub fn into_reader(self) -> Reader<'a,> { + self.builder.into_reader().into() + } + pub fn reborrow(&mut self) -> Builder<'_,> { + Builder { builder: self.builder.reborrow() } + } + pub fn reborrow_as_reader(&self) -> Reader<'_,> { + self.builder.as_reader().into() + } + + pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> { + self.builder.as_reader().total_size() + } + #[inline] + pub fn get_id(self) -> ::capnp::Result<::capnp::text::Builder<'a>> { + ::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(0), ::core::option::Option::None) + } + #[inline] + pub fn set_id(&mut self, value: ::capnp::text::Reader<'_>) { + self.builder.reborrow().get_pointer_field(0).set_text(value); + } + #[inline] + pub fn init_id(self, size: u32) -> ::capnp::text::Builder<'a> { + self.builder.get_pointer_field(0).init_text(size) + } + #[inline] + pub fn has_id(&self) -> bool { + !self.builder.is_pointer_field_null(0) + } + #[inline] + pub fn get_author(self) -> ::capnp::Result<::capnp::text::Builder<'a>> { + ::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(1), ::core::option::Option::None) + } + #[inline] + pub fn set_author(&mut self, value: ::capnp::text::Reader<'_>) { + self.builder.reborrow().get_pointer_field(1).set_text(value); + } + #[inline] + pub fn init_author(self, size: u32) -> ::capnp::text::Builder<'a> { + self.builder.get_pointer_field(1).init_text(size) + } + #[inline] + pub fn has_author(&self) -> bool { + !self.builder.is_pointer_field_null(1) + } + #[inline] + pub fn get_content(self) -> ::capnp::Result<::capnp::text::Builder<'a>> { + ::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(2), ::core::option::Option::None) + } + #[inline] + pub fn set_content(&mut self, value: ::capnp::text::Reader<'_>) { + self.builder.reborrow().get_pointer_field(2).set_text(value); + } + #[inline] + pub fn init_content(self, size: u32) -> ::capnp::text::Builder<'a> { + self.builder.get_pointer_field(2).init_text(size) + } + #[inline] + pub fn has_content(&self) -> bool { + !self.builder.is_pointer_field_null(2) + } + } + + pub struct Pipeline { _typeless: ::capnp::any_pointer::Pipeline } + impl ::capnp::capability::FromTypelessPipeline for Pipeline { + fn new(typeless: ::capnp::any_pointer::Pipeline) -> Self { + Self { _typeless: typeless, } + } + } + impl Pipeline { + } + mod _private { + pub static ENCODED_NODE: [::capnp::Word; 62] = [ + ::capnp::word(0, 0, 0, 0, 5, 0, 6, 0), + ::capnp::word(50, 25, 14, 89, 91, 12, 143, 231), + ::capnp::word(13, 0, 0, 0, 1, 0, 0, 0), + ::capnp::word(164, 172, 216, 255, 36, 223, 58, 242), + ::capnp::word(3, 0, 7, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(21, 0, 0, 0, 178, 0, 0, 0), + ::capnp::word(29, 0, 0, 0, 7, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(25, 0, 0, 0, 175, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(109, 111, 100, 101, 108, 115, 46, 99), + ::capnp::word(97, 112, 110, 112, 58, 76, 111, 103), + ::capnp::word(69, 118, 101, 110, 116, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 1, 0, 1, 0), + ::capnp::word(12, 0, 0, 0, 3, 0, 4, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 1, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(69, 0, 0, 0, 26, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(64, 0, 0, 0, 3, 0, 1, 0), + ::capnp::word(76, 0, 0, 0, 2, 0, 1, 0), + ::capnp::word(1, 0, 0, 0, 1, 0, 0, 0), + ::capnp::word(0, 0, 1, 0, 1, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(73, 0, 0, 0, 58, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(68, 0, 0, 0, 3, 0, 1, 0), + ::capnp::word(80, 0, 0, 0, 2, 0, 1, 0), + ::capnp::word(2, 0, 0, 0, 2, 0, 0, 0), + ::capnp::word(0, 0, 1, 0, 2, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(77, 0, 0, 0, 66, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(72, 0, 0, 0, 3, 0, 1, 0), + ::capnp::word(84, 0, 0, 0, 2, 0, 1, 0), + ::capnp::word(105, 100, 0, 0, 0, 0, 0, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(97, 117, 116, 104, 111, 114, 0, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(99, 111, 110, 116, 101, 110, 116, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(12, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ::capnp::word(0, 0, 0, 0, 0, 0, 0, 0), + ]; + pub fn get_field_types(index: u16) -> ::capnp::introspect::Type { + match index { + 0 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(), + 1 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(), + 2 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(), + _ => panic!("invalid field index {}", index), + } + } + pub fn get_annotation_types(child_index: Option, index: u32) -> ::capnp::introspect::Type { + panic!("invalid annotation indices ({:?}, {}) ", child_index, index) + } + pub static RAW_SCHEMA: ::capnp::introspect::RawStructSchema = ::capnp::introspect::RawStructSchema { + encoded_node: &ENCODED_NODE, + nonunion_members: NONUNION_MEMBERS, + members_by_discriminant: MEMBERS_BY_DISCRIMINANT, + }; + pub static NONUNION_MEMBERS : &[u16] = &[0,1,2]; + pub static MEMBERS_BY_DISCRIMINANT : &[u16] = &[]; + pub const TYPE_ID: u64 = 0xe78f_0c5b_590e_1932; + } +} diff --git a/crates/churn-domain/src/lib.rs b/crates/churn-domain/src/lib.rs index 0d90a17..10b65a4 100644 --- a/crates/churn-domain/src/lib.rs +++ b/crates/churn-domain/src/lib.rs @@ -23,3 +23,20 @@ pub struct ServerMonitorResp { pub cursor: Option, pub logs: Vec, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LogEvent { + pub id: uuid::Uuid, + pub author: String, + pub content: String, +} + +impl LogEvent { + pub fn new(author: impl Into, content: impl Into) -> Self { + Self { + id: uuid::Uuid::new_v4(), + author: author.into(), + content: content.into(), + } + } +} diff --git a/crates/churn-server/Cargo.toml b/crates/churn-server/Cargo.toml index 4189aad..4915a3e 100644 --- a/crates/churn-server/Cargo.toml +++ b/crates/churn-server/Cargo.toml @@ -11,6 +11,7 @@ publish.workspace = true [dependencies] churn-domain.workspace = true +churn-capnp.workspace = true anyhow.workspace = true tokio.workspace = true @@ -22,3 +23,6 @@ axum.workspace = true serde.workspace = true serde_json.workspace = true uuid.workspace = true +async-trait.workspace = true + +sled.workspace = true diff --git a/crates/churn-server/src/db.rs b/crates/churn-server/src/db.rs new file mode 100644 index 0000000..8b47077 --- /dev/null +++ b/crates/churn-server/src/db.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use async_trait::async_trait; +use churn_domain::ServerEnrollReq; +use tokio::sync::Mutex; + +#[derive(Clone)] +pub struct Db(Arc); + +impl Db { + pub fn new_sled(path: &Path) -> Self { + Self(Arc::new(DefaultDb::new(path))) + } +} + +impl std::ops::Deref for Db { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for Db { + fn default() -> Self { + Self(Arc::new(DefaultDb::default())) + } +} + +struct DefaultDb { + db: sled::Db, +} + +impl Default for DefaultDb { + fn default() -> Self { + Self::new(&PathBuf::from("churn-server.sled")) + } +} + +impl DefaultDb { + pub fn new(path: &Path) -> Self { + Self { + db: sled::open(path).expect("to be able open a sled path"), + } + } +} + +#[async_trait] +pub trait DbTrait { + async fn insert(&self, namespace: &str, key: &str, value: &str) -> anyhow::Result<()>; + async fn get_all(&self, namespace: &str) -> anyhow::Result>; +} + +#[async_trait] +impl DbTrait for DefaultDb { + async fn insert(&self, namespace: &str, key: &str, value: &str) -> anyhow::Result<()> { + let tree = self.db.open_tree(namespace)?; + + tree.insert(key, value)?; + + tree.flush_async().await?; + + Ok(()) + } + + async fn get_all(&self, namespace: &str) -> anyhow::Result> { + let tree = self.db.open_tree(namespace)?; + + Ok(tree + .iter() + .flatten() + .map(|(_, val)| val) + .flat_map(|v| v.iter().map(|v| v.to_string()).collect::>()) + .collect::>()) + } +} diff --git a/crates/churn-server/src/event.rs b/crates/churn-server/src/event.rs index 13ea61c..afdfd99 100644 --- a/crates/churn-server/src/event.rs +++ b/crates/churn-server/src/event.rs @@ -4,13 +4,23 @@ use std::sync::Arc; use axum::async_trait; -use churn_domain::ServerEnrollReq; +use churn_domain::{LogEvent, ServerEnrollReq}; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use tokio::sync::{Mutex, RwLock}; +use churn_capnp::CapnpPackExt; + +use crate::db::Db; + #[derive(Clone)] pub struct EventService(Arc); +impl EventService { + pub fn new(db: Db) -> Self { + Self(Arc::new(DefaultEventService::new(db))) + } +} + impl std::ops::Deref for EventService { type Target = Arc; @@ -27,23 +37,12 @@ impl Default for EventService { #[derive(Default)] struct DefaultEventService { - agents: Arc>>, + db: Db, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LogEvent { - pub id: uuid::Uuid, - pub author: String, - pub content: String, -} - -impl LogEvent { - pub fn new(author: impl Into, content: impl Into) -> Self { - Self { - id: uuid::Uuid::new_v4(), - author: author.into(), - content: content.into(), - } +impl DefaultEventService { + pub fn new(db: Db) -> Self { + Self { db } } } @@ -57,22 +56,34 @@ pub trait EventServiceTrait { #[async_trait] impl EventServiceTrait for DefaultEventService { async fn append(&self, req: LogEvent) -> anyhow::Result<()> { - let mut events = self.agents.write().await; - events.push(req); + self.db + .insert("events_log", &req.id.to_string(), &req.serialize_capnp()) + .await; + Ok(()) } async fn get_from_cursor(&self, cursor: uuid::Uuid) -> anyhow::Result> { - let events = self.agents.read().await; - let items = events + let events = self.db.get_all("events_log").await?; + + let events = events .iter() + .map(|e| LogEvent::deserialize_capnp(e)) + .flatten() .skip_while(|item| item.id != cursor) .skip(1) - .cloned() - .collect::>(); - Ok(items) + .collect(); + + Ok(events) } async fn get_from_beginning(&self) -> anyhow::Result> { - let events = self.agents.read().await; - Ok(events.iter().cloned().collect()) + let events = self.db.get_all("events_log").await?; + + let events = events + .iter() + .map(|e| LogEvent::deserialize_capnp(e)) + .flatten() + .collect(); + + Ok(events) } } diff --git a/crates/churn-server/src/main.rs b/crates/churn-server/src/main.rs index 505fc64..ac55f21 100644 --- a/crates/churn-server/src/main.rs +++ b/crates/churn-server/src/main.rs @@ -1,8 +1,10 @@ mod agent; +mod db; mod event; mod lease; use std::net::SocketAddr; +use std::path::PathBuf; use agent::AgentService; use anyhow::Error; @@ -11,18 +13,37 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::{Json, Router}; -use churn_domain::{LeaseResp, ServerEnrollReq, ServerMonitorResp}; -use clap::{Parser, Subcommand}; -use event::{EventService, LogEvent}; +use churn_domain::{LeaseResp, LogEvent, ServerEnrollReq, ServerMonitorResp}; +use clap::{Args, Parser, Subcommand, ValueEnum}; +use event::EventService; use lease::LeaseService; use serde::{Deserialize, Serialize}; use serde_json::json; +use crate::db::Db; + #[derive(Parser)] #[command(author, version, about, long_about = None, subcommand_required = true)] struct Command { #[command(subcommand)] command: Option, + + #[clap(flatten)] + global: GlobalArgs, +} + +#[derive(Args)] +struct GlobalArgs { + #[arg(env = "CHURN_DATABASE", long, default_value = "sled")] + database: DatabaseType, + + #[arg(env = "CHURN_SLED_PATH", long, default_value = "churn-server.sled")] + sled_path: PathBuf, +} + +#[derive(ValueEnum, Clone)] +enum DatabaseType { + Sled, } #[derive(Subcommand)] @@ -55,6 +76,9 @@ async fn main() -> anyhow::Result<()> { match cli.command { Some(Commands::Serve { host }) => { tracing::info!("Starting churn server"); + let db = match cli.global.database { + DatabaseType::Sled => Db::new_sled(&cli.global.sled_path), + }; let app = Router::new() .route("/ping", get(ping)) @@ -70,7 +94,7 @@ async fn main() -> anyhow::Result<()> { .with_state(AppState { agent: AgentService::default(), leases: LeaseService::default(), - events: EventService::default(), + events: EventService::new(db), }); tracing::info!("churn server listening on {}", host);