diff --git a/Cargo.lock b/Cargo.lock index ac4e5ac..6c97f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,8 +70,8 @@ name = "api" version = "0.1.0" dependencies = [ "atproto", + "axum", "http 1.3.1", - "router", "serde", "serde_json", "tokio", @@ -123,7 +123,6 @@ dependencies = [ "time", "tracing", "tracing-subscriber", - "unicode-segmentation", ] [[package]] @@ -294,9 +293,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.6.4" +version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61138465baf186c63e8d9b6b613b508cd832cba4ce93cf37ce5f096f91ac1a6" +checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" dependencies = [ "bon-macros", "rustversion", @@ -304,9 +303,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.4" +version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d1dad34aa19bf02295382f08d9bc40651585bd497266831d40ee6296fb49ca" +checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" dependencies = [ "darling", "ident_case", @@ -663,20 +662,6 @@ dependencies = [ "serde", ] -[[package]] -name = "entryway" -version = "0.1.0" -dependencies = [ - "atproto", - "http 1.3.1", - "router", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1828,21 +1813,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "router" -version = "0.1.0" -dependencies = [ - "atproto", - "axum", - "bon", - "http 1.3.1", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "rsa" version = "0.9.8" @@ -2738,12 +2708,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unsigned-varint" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 55a82e5..c029fb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,11 @@ [workspace] resolver = "3" -members = [ "api", "atproto", "entryway", "db", "router", "ingestor" ] +members = [ "api", "atproto","db", "ingestor"] [workspace.dependencies] async-trait = "0.1.88" atproto = { path = "./atproto" } -db = { path = "./db" } -router = { path = "./router" } -serde = { version = "1.0.219", features = ["derive"] } +serde = "1.0.219" serde_json = "1.0.140" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio"] } thiserror = "2.0.12" diff --git a/api/Cargo.toml b/api/Cargo.toml index 5dcc6e0..1fc049c 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] atproto.workspace = true -router.workspace = true +axum = { version = "0.8.3", features = ["json"] } http = "1.3.1" serde.workspace = true serde_json.workspace = true diff --git a/api/src/main.rs b/api/src/main.rs index 13040a6..46e17ae 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -1,4 +1,4 @@ -use router::{ +use crate::router::{ Router, Endpoint, xrpc::{ diff --git a/api/src/router.rs b/api/src/router.rs new file mode 100644 index 0000000..bfa3b17 --- /dev/null +++ b/api/src/router.rs @@ -0,0 +1,59 @@ +use crate::router::xrpc::{ + XrpcEndpoint, + XrpcHandler, + QueryInput, + ProcedureInput, +}; +use atproto::Nsid; +use axum::Router as AxumRouter; +use core::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr}; +use tokio::net::TcpListener; + +pub struct Router { + addr: SocketAddr, + router: AxumRouter, +} + +// In case server ever needs to support more than just XRPC +pub enum Endpoint { + Xrpc(XrpcEndpoint), +} +impl Endpoint { + pub fn new_xrpc_query(nsid: Nsid, query: Q) -> Self + where + Q: XrpcHandler + Clone + { + Endpoint::Xrpc(XrpcEndpoint::new_query(nsid,query)) + } + pub fn new_xrpc_procedure

(nsid: Nsid, procedure: P) -> Self + where + P: XrpcHandler + Clone + { + Endpoint::Xrpc(XrpcEndpoint::new_procedure(nsid,procedure)) + } +} + +pub mod xrpc; + +impl Router { + pub fn new() -> Self { + let mut router = AxumRouter::new(); + router = XrpcEndpoint::not_implemented().add_to_router(router); + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 6702); + Router { router, addr } + } + + pub fn add_endpoint(mut self, endpoint: Endpoint) -> Self { + match endpoint { + Endpoint::Xrpc(ep) => self.router = ep.add_to_router(self.router), + }; + self + } + + pub async fn serve(self) { + let listener = TcpListener::bind(self.addr).await.unwrap(); + + axum::serve(listener, self.router).await.unwrap(); + } +} diff --git a/router/src/xrpc.rs b/api/src/router/xrpc.rs similarity index 72% rename from router/src/xrpc.rs rename to api/src/router/xrpc.rs index 0ed8c36..500f331 100644 --- a/router/src/xrpc.rs +++ b/api/src/router/xrpc.rs @@ -1,10 +1,9 @@ -use crate::Endpoint; use std::{ collections::HashMap, pin::Pin, future::Future, }; -use atproto::types::Nsid; +use atproto::Nsid; use axum::{ extract::{ Json, @@ -53,7 +52,7 @@ pub fn response(code: StatusCode, message: &str) -> Response { } pub struct QueryInput { - pub parameters: HashMap, + parameters: HashMap, } impl FromRequestParts for QueryInput where @@ -70,16 +69,12 @@ where } } } - -#[derive(Debug)] -pub struct ProcedureInput { - pub parameters: HashMap, - pub input: J, +pub struct ProcedureInput { + parameters: HashMap, + input: Json, } - -impl FromRequest for ProcedureInput +impl FromRequest for ProcedureInput where - J: for<'de> serde::Deserialize<'de> + Send + 'static, Bytes: FromRequest, S: Send + Sync, { @@ -87,13 +82,17 @@ where async fn from_request(req: Request, state: &S) -> Result { - let parameters: HashMap = Query::try_from_uri(req.uri()) - .map(|p| p.0) - .map_err(|e| error(StatusCode::BAD_REQUEST, "Bad Paramters", &e.body_text()))?; + let query_params: Result>, QueryRejection> = Query::try_from_uri(req.uri()); + let parameters = match query_params { + Ok(p) => p.0, + Err(e) => return Err(error(StatusCode::BAD_REQUEST, "Bad Parameters", &e.body_text())), + }; - let input: J = Json::::from_request(req, state).await - .map(|Json(v)| v) - .map_err(|e| error(StatusCode::BAD_REQUEST, "Bad Parameters", &e.body_text()))?; + let json_value = Json::::from_request(req, state).await; + let input: Json = match json_value { + Ok(v) => v, + Err(e) => return Err(error(StatusCode::BAD_REQUEST, "Bad Parameters", &e.body_text())), + }; Ok(ProcedureInput { parameters, input }) } @@ -113,12 +112,12 @@ where Box::pin((self)(input)) } } -impl XrpcHandler> for F +impl XrpcHandler for F where - F: Fn(ProcedureInput) -> Fut + Send + Sync + 'static, + F: Fn(ProcedureInput) -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { - fn call(&self, input: ProcedureInput) + fn call(&self, input: ProcedureInput) -> Pin+ Send>> { Box::pin((self)(input)) } @@ -140,15 +139,14 @@ impl XrpcEndpoint { } } - pub fn new_procedure(nsid: Nsid, procedure: P) -> Self + pub fn new_procedure

(nsid: Nsid, procedure: P) -> Self where - P: XrpcHandler> + Clone, - J: for<'de> serde::Deserialize<'de> + Send + 'static, + P: XrpcHandler + Clone { XrpcEndpoint { path: Path::Nsid(nsid), resolver: post(async move | req: Request | -> Response { - match ProcedureInput::::from_request(req, &()).await { + match ProcedureInput::from_request(req, &()).await { Ok(pi) => procedure.call(pi).await, Err(e) => e } @@ -156,6 +154,15 @@ impl XrpcEndpoint { } } + pub fn add_to_router(self, router: axumRouter) -> axumRouter { + let path = match self.path { + Path::Nsid(nsid) => &("/xrpc/".to_owned() + nsid.as_str()), + Path::NotImplemented => "/xrpc/{*nsid}", + }; + + router.route(path, self.resolver) + } + pub fn not_implemented() -> Self { let resolver = ( StatusCode::NOT_IMPLEMENTED, @@ -172,13 +179,3 @@ impl XrpcEndpoint { } } -impl Endpoint for XrpcEndpoint { - fn add_to_router(self, router: axumRouter) -> axumRouter { - let path = match self.path { - Path::Nsid(nsid) => &("/xrpc/".to_owned() + &nsid.to_string()), - Path::NotImplemented => "/xrpc/{*nsid}", - }; - - router.route(path, self.resolver) - } -} diff --git a/atproto/Cargo.toml b/atproto/Cargo.toml index 0881493..3f5445b 100644 --- a/atproto/Cargo.toml +++ b/atproto/Cargo.toml @@ -13,7 +13,6 @@ time = { version = "0.3.41", features = ["parsing", "formatting"] } tracing-subscriber.workspace = true tracing.workspace = true thiserror.workspace = true -unicode-segmentation = "1.9.0" [features] default = [] diff --git a/atproto/src/error.rs b/atproto/src/error.rs index e320a61..1db7106 100644 --- a/atproto/src/error.rs +++ b/atproto/src/error.rs @@ -20,12 +20,8 @@ pub enum FormatError { pub enum ParseError { #[error("Time Parse Error: {0}")] Datetime(#[from] time::error::Parse), - #[error("Json Parse Error: {0}")] - Serde(#[from] serde_json::error::Error), #[error("Length of parsed object too long, max: {max:?}, got: {got:?}.")] Length { max: usize, got: usize }, - #[error("Length of parsed object too short, min: {min:?}, got: {got:?}.")] - MinLength { min: usize, got: usize }, #[error("Currently Did is enforced, cannot use handle, {handle:?}")] ForceDid { handle: String }, #[error("Incorrectly formatted")] diff --git a/atproto/src/lexicons/mod.rs b/atproto/src/lexicons/mod.rs index 96aefa7..8107df0 100644 --- a/atproto/src/lexicons/mod.rs +++ b/atproto/src/lexicons/mod.rs @@ -1 +1,3 @@ -pub mod myspoor; +// @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. +pub mod record; +pub mod my; diff --git a/atproto/src/lexicons/my.rs b/atproto/src/lexicons/my.rs new file mode 100644 index 0000000..ac9c6bd --- /dev/null +++ b/atproto/src/lexicons/my.rs @@ -0,0 +1,3 @@ +// @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. +//!Definitions for the `my` namespace. +pub mod spoor; diff --git a/atproto/src/lexicons/my/spoor.rs b/atproto/src/lexicons/my/spoor.rs new file mode 100644 index 0000000..172fa6a --- /dev/null +++ b/atproto/src/lexicons/my/spoor.rs @@ -0,0 +1,4 @@ +// @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. +//!Definitions for the `my.spoor` namespace. +pub mod content; +pub mod log; diff --git a/atproto/src/lexicons/myspoor/content.rs b/atproto/src/lexicons/my/spoor/content.rs similarity index 100% rename from atproto/src/lexicons/myspoor/content.rs rename to atproto/src/lexicons/my/spoor/content.rs diff --git a/atproto/src/lexicons/myspoor/content/external.rs b/atproto/src/lexicons/my/spoor/content/external.rs similarity index 100% rename from atproto/src/lexicons/myspoor/content/external.rs rename to atproto/src/lexicons/my/spoor/content/external.rs diff --git a/atproto/src/lexicons/myspoor/content/media.rs b/atproto/src/lexicons/my/spoor/content/media.rs similarity index 100% rename from atproto/src/lexicons/myspoor/content/media.rs rename to atproto/src/lexicons/my/spoor/content/media.rs diff --git a/atproto/src/lexicons/myspoor/content/title.rs b/atproto/src/lexicons/my/spoor/content/title.rs similarity index 100% rename from atproto/src/lexicons/myspoor/content/title.rs rename to atproto/src/lexicons/my/spoor/content/title.rs diff --git a/atproto/src/lexicons/my/spoor/log.rs b/atproto/src/lexicons/my/spoor/log.rs new file mode 100644 index 0000000..87f699e --- /dev/null +++ b/atproto/src/lexicons/my/spoor/log.rs @@ -0,0 +1,16 @@ +// @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. +//!Definitions for the `my.spoor.log` namespace. +pub mod activity; +pub mod session; +#[derive(Debug)] +pub struct Activity; +impl atrium_api::types::Collection for Activity { + const NSID: &'static str = "my.spoor.log.activity"; + type Record = activity::Record; +} +#[derive(Debug)] +pub struct Session; +impl atrium_api::types::Collection for Session { + const NSID: &'static str = "my.spoor.log.session"; + type Record = session::Record; +} diff --git a/atproto/src/lexicons/myspoor/log/activity.rs b/atproto/src/lexicons/my/spoor/log/activity.rs similarity index 100% rename from atproto/src/lexicons/myspoor/log/activity.rs rename to atproto/src/lexicons/my/spoor/log/activity.rs diff --git a/atproto/src/lexicons/myspoor/log/session.rs b/atproto/src/lexicons/my/spoor/log/session.rs similarity index 100% rename from atproto/src/lexicons/myspoor/log/session.rs rename to atproto/src/lexicons/my/spoor/log/session.rs diff --git a/atproto/src/lexicons/myspoor/log.rs b/atproto/src/lexicons/myspoor/log.rs deleted file mode 100644 index 9a008a8..0000000 --- a/atproto/src/lexicons/myspoor/log.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde::Deserialize; -use crate::types::{BoundString, StrongRef, Uri, Did, Datetime}; - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Session { - pub content: StrongRef, - pub label: Option>, - pub other_participants: Option>, - pub created_at: Datetime, -} diff --git a/atproto/src/lexicons/myspoor/mod.rs b/atproto/src/lexicons/myspoor/mod.rs deleted file mode 100644 index f2d6b27..0000000 --- a/atproto/src/lexicons/myspoor/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod content; -pub mod log; diff --git a/atproto/src/lexicons/record.rs b/atproto/src/lexicons/record.rs new file mode 100644 index 0000000..290d286 --- /dev/null +++ b/atproto/src/lexicons/record.rs @@ -0,0 +1,65 @@ +// @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. +//!A collection of known record types. +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "$type")] +pub enum KnownRecord { + #[serde(rename = "my.spoor.content.external")] + LexiconsMySpoorContentExternal( + Box, + ), + #[serde(rename = "my.spoor.content.media")] + LexiconsMySpoorContentMedia(Box), + #[serde(rename = "my.spoor.log.activity")] + LexiconsMySpoorLogActivity(Box), + #[serde(rename = "my.spoor.log.session")] + LexiconsMySpoorLogSession(Box), +} +impl From for KnownRecord { + fn from(record: crate::lexicons::my::spoor::content::external::Record) -> Self { + KnownRecord::LexiconsMySpoorContentExternal(Box::new(record)) + } +} +impl From for KnownRecord { + fn from( + record_data: crate::lexicons::my::spoor::content::external::RecordData, + ) -> Self { + KnownRecord::LexiconsMySpoorContentExternal(Box::new(record_data.into())) + } +} +impl From for KnownRecord { + fn from(record: crate::lexicons::my::spoor::content::media::Record) -> Self { + KnownRecord::LexiconsMySpoorContentMedia(Box::new(record)) + } +} +impl From for KnownRecord { + fn from( + record_data: crate::lexicons::my::spoor::content::media::RecordData, + ) -> Self { + KnownRecord::LexiconsMySpoorContentMedia(Box::new(record_data.into())) + } +} +impl From for KnownRecord { + fn from(record: crate::lexicons::my::spoor::log::activity::Record) -> Self { + KnownRecord::LexiconsMySpoorLogActivity(Box::new(record)) + } +} +impl From for KnownRecord { + fn from(record_data: crate::lexicons::my::spoor::log::activity::RecordData) -> Self { + KnownRecord::LexiconsMySpoorLogActivity(Box::new(record_data.into())) + } +} +impl From for KnownRecord { + fn from(record: crate::lexicons::my::spoor::log::session::Record) -> Self { + KnownRecord::LexiconsMySpoorLogSession(Box::new(record)) + } +} +impl From for KnownRecord { + fn from(record_data: crate::lexicons::my::spoor::log::session::RecordData) -> Self { + KnownRecord::LexiconsMySpoorLogSession(Box::new(record_data.into())) + } +} +impl Into for KnownRecord { + fn into(self) -> atrium_api::types::Unknown { + atrium_api::types::TryIntoUnknown::try_into_unknown(&self).unwrap() + } +} diff --git a/atproto/src/lib.rs b/atproto/src/lib.rs index 021b86c..53c8d32 100644 --- a/atproto/src/lib.rs +++ b/atproto/src/lib.rs @@ -1,7 +1,5 @@ -// pub mod lexicons; +pub mod lexicons; pub mod types; pub mod error; #[cfg(feature = "sqlx-support")] pub mod sqlx; - -pub use atrium_api::types::Collection; diff --git a/atproto/src/types.rs b/atproto/src/types.rs index 2e1c387..26ee60d 100644 --- a/atproto/src/types.rs +++ b/atproto/src/types.rs @@ -1,20 +1,5 @@ use crate::error::{Error, ParseError}; -#[macro_export] -macro_rules! basic_deserializer { - ($name:ident) => { - impl<'de> serde::de::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let value: String = serde::de::Deserialize::deserialize(deserializer)?; - value.parse::<$name>().map_err(::custom) - } - } - } -} - macro_rules! basic_string_type { ($name:ident, $regex:literal, $max_len:literal) => { pub struct $name { value: String, } @@ -50,26 +35,20 @@ macro_rules! basic_string_type { }) } } - - basic_deserializer!($name); } } -mod authority; -mod bound_string; -mod cid; -mod datetime; mod did; -mod record_key; -mod strong_ref; -mod uri; -pub use authority::Authority; -pub use bound_string::BoundString; -pub use cid::Cid; -pub use datetime::Datetime; pub use did::Did; +mod cid; +pub use cid::Cid; +mod authority; +pub use authority::Authority; +mod datetime; +pub use datetime::Datetime; +mod record_key; pub use record_key::RecordKey; -pub use strong_ref::StrongRef; +mod uri; pub use uri::Uri; basic_string_type!(Handle, @@ -84,3 +63,22 @@ basic_string_type!(Tid, r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$", 13 ); + +pub struct StrongRef { + content: T, + cid: Cid, +} + +impl StrongRef { + pub fn get_content(&self) -> &T { + &self.content + } + + pub fn extract_content(self) -> (T, Cid) { + (self.content, self.cid) + } + + pub fn get_cid(&self) -> &Cid { + &self.cid + } +} diff --git a/atproto/src/types/authority.rs b/atproto/src/types/authority.rs index ad3d68c..1c76750 100644 --- a/atproto/src/types/authority.rs +++ b/atproto/src/types/authority.rs @@ -1,16 +1,12 @@ use crate::{ - types::{ - Did, Handle - }, + types::{Did, Handle}, error::{Error, ParseError}, }; -use serde::Deserialize; use std::{ fmt::{Display, Formatter, Result as FmtResult}, str::FromStr, }; -#[derive(Deserialize)] pub enum Authority { Did(Did), Handle(Handle), diff --git a/atproto/src/types/bound_string.rs b/atproto/src/types/bound_string.rs deleted file mode 100644 index db81bcf..0000000 --- a/atproto/src/types/bound_string.rs +++ /dev/null @@ -1,57 +0,0 @@ -use unicode_segmentation::UnicodeSegmentation; -use crate::error::{Error, ParseError}; -use std::{ - fmt::{Display, Formatter, Result as FmtResult}, - str::FromStr, -}; - -pub struct BoundString< - const MIN: usize, const MAX: usize> -{ - value: String, -} - -impl BoundString { - fn check_length(s: &str) -> Result<(), Error> { - let grapheme_count: usize = s.graphemes(true).take(MAX + 1).count(); - if grapheme_count > MAX { - return Err(Error::Parse { - err: ParseError::Length { max: MAX, got: grapheme_count }, - object: "String".to_string(), - }); - } - if grapheme_count < MIN { - return Err(Error::Parse { - err: ParseError::MinLength { min: MIN, got: grapheme_count }, - object: "String".to_string(), - }); - } - Ok(()) - } -} - -impl Display for BoundString { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, "{}", self.value) - } -} - -impl FromStr for BoundString { - type Err = Error; - fn from_str(s: &str) -> Result { - Self::check_length(s)?; - - Ok(BoundString { value: s.to_string() }) - } -} - -impl<'de, const MIN: usize, const MAX: usize> serde::de::Deserialize<'de> - for BoundString { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let value: String = serde::de::Deserialize::deserialize(deserializer)?; - value.parse::>().map_err(::custom) - } -} diff --git a/atproto/src/types/cid.rs b/atproto/src/types/cid.rs index a1d3ff2..3581ad2 100644 --- a/atproto/src/types/cid.rs +++ b/atproto/src/types/cid.rs @@ -1,7 +1,4 @@ -use crate::{ - basic_deserializer, - error::Error -}; +use crate::error::Error; pub struct Cid { value: String, } @@ -17,5 +14,3 @@ impl std::str::FromStr for Cid { Ok(Self { value: s.to_string() }) } } - -basic_deserializer!(Cid); diff --git a/atproto/src/types/datetime.rs b/atproto/src/types/datetime.rs index bd5583b..6dcf234 100644 --- a/atproto/src/types/datetime.rs +++ b/atproto/src/types/datetime.rs @@ -1,7 +1,4 @@ -use crate::{ - basic_deserializer, - error::{Error, ParseError, FormatError}, -}; +use crate::error::{Error, ParseError, FormatError}; use time::{ UtcDateTime, format_description::well_known::{Rfc3339, Iso8601}, @@ -64,5 +61,3 @@ impl FromStr for Datetime { Ok(Datetime { time: datetime, derived_string: s.to_string() }) } } - -basic_deserializer!(Datetime); diff --git a/atproto/src/types/did.rs b/atproto/src/types/did.rs index fa9b739..6c91db2 100644 --- a/atproto/src/types/did.rs +++ b/atproto/src/types/did.rs @@ -1,7 +1,4 @@ -use crate::{ - basic_deserializer, - error::{Error, ParseError}, -}; +use crate::error::{Error, ParseError}; use std::{ fmt::{Display, Formatter, Result as FmtResult}, str::FromStr, @@ -34,8 +31,6 @@ impl FromStr for DidMethod { } } -basic_deserializer!(DidMethod); - pub struct Did { method: DidMethod, identifier: String, @@ -75,5 +70,3 @@ impl FromStr for Did { }) } } - -basic_deserializer!(Did); diff --git a/atproto/src/types/record_key.rs b/atproto/src/types/record_key.rs index 8542029..7368b53 100644 --- a/atproto/src/types/record_key.rs +++ b/atproto/src/types/record_key.rs @@ -61,5 +61,3 @@ impl FromStr for RecordKey { Ok(RecordKey::Any(s.to_string())) } } - -basic_deserializer!(RecordKey); diff --git a/atproto/src/types/strong_ref.rs b/atproto/src/types/strong_ref.rs deleted file mode 100644 index 9e23ff7..0000000 --- a/atproto/src/types/strong_ref.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{ - basic_deserializer, - types::{Cid, Uri}, - error::{Error, ParseError}, -}; - -pub struct StrongRef { - content: T, - cid: Cid, -} - -impl StrongRef { - pub fn from_atrium_api(strong_ref: atrium_api::com::atproto::repo::strong_ref::MainData) -> Result { - let str_cid = serde_json::to_string(&strong_ref.cid).map_err(|e| { - Error::Parse { err: ParseError::Serde(e), object: "Uri".to_string() } - })?; - Ok(Self { - content: strong_ref.uri.parse::()?, - cid: str_cid.parse::()?, - }) - } -} - -impl StrongRef { - pub fn map_content(self, f: F) -> StrongRef - where - F: FnOnce(T) -> U, - { - StrongRef { - content: f(self.content), - cid: self.cid, - } - } - - pub fn get_content(&self) -> &T { - &self.content - } - - pub fn extract_content(self) -> (T, Cid) { - (self.content, self.cid) - } - - pub fn get_cid(&self) -> &Cid { - &self.cid - } -} diff --git a/atproto/src/types/uri.rs b/atproto/src/types/uri.rs index 1fbf423..843ee2b 100644 --- a/atproto/src/types/uri.rs +++ b/atproto/src/types/uri.rs @@ -1,5 +1,4 @@ use crate::{ - basic_deserializer, types::{Did, Authority, Nsid, RecordKey}, error::{Error, ParseError}, }; @@ -34,78 +33,47 @@ impl Display for Uri { impl FromStr for Uri { type Err = Error; fn from_str(s: &str) -> Result { - Self::check_length(s)?; - - let Some(( - _whole, unchecked_authority, unchecked_collection, unchecked_rkey - )): Option<(&str, &str, &str, &str)> = regex_captures!( - r"/^at:\/\/([\w\.\-_~:]+)(?:\/([\w\.\-_~:]+)(?:)\/([\w\.\-_~:]+))?$/i", - s, - ) else { - return Err(Error::Parse { - err: ParseError::Format, - object: "Uri".to_string(), - }); - }; - - let did = Self::check_authority(unchecked_authority.to_string())?; - - let collection = if unchecked_collection.is_empty() { None } - else { Some(Self::check_collection(unchecked_collection.to_string())?) }; - - let rkey = if unchecked_rkey.is_empty() { None } - else { Some(Self::check_rkey(unchecked_rkey.to_string())?) }; - - Ok(Uri { authority: did, collection, rkey }) - } -} - -impl Uri { - pub fn from_components( - authority_str: String, collection_str: Option, - rkey_str: Option - ) -> Result { - let authority = Self::check_authority(authority_str)?; - let collection = collection_str.map(Self::check_collection).transpose()?; - let rkey = rkey_str.map(Self::check_rkey).transpose()?; - let uri = Uri { authority, collection, rkey }; - Self::check_length(&uri.to_string())?; - - Ok(uri) - } - - fn check_length(s: &str) -> Result<(), Error> { if s.len() > 8000 { return Err(Error::Parse { err: ParseError::Length { max: 8000, got: s.len() }, object: "Did".to_string(), }); } - Ok(()) - } - fn check_authority(authority: String) -> Result { - Ok(match Authority::from_str(&authority)? { + let Some(( + _whole, unchecked_authority, unchecked_collection, unchecked_rkey + )) = regex_captures!( + r"/^at:\/\/([\w\.\-_~:]+)(?:\/([\w\.\-_~:]+)(?:)\/([\w\.\-_~:]+))?$/i", + s, + ) else { + return Err(Error::Parse { + err: ParseError::Format, + object: "Uri".to_string(), + }); + }; + + let did = match Authority::from_str(unchecked_authority)? { Authority::Handle(h) => return Err(Error::Parse { err: ParseError::ForceDid { handle: h.to_string() }, object: "Uri".to_string(), }), Authority::Did(d) => d, - }) - } + }; - fn check_collection(collection: String) -> Result { - Ok(collection.parse::()?) - } + let collection = if unchecked_collection.is_empty() { None } + else { Some(unchecked_collection.parse::()?) }; - fn check_rkey(rkey: String) -> Result { - Ok(rkey.parse::()?) - } + let rkey = if unchecked_rkey.is_empty() { None } + else { Some(unchecked_rkey.parse::()?) }; + Ok(Uri { authority: did, collection, rkey }) + } +} + +impl Uri { pub fn authority_as_did(&self) -> &Did { &self.authority } } -basic_deserializer!(Uri); diff --git a/entryway/Cargo.toml b/entryway/Cargo.toml deleted file mode 100644 index 47e68d8..0000000 --- a/entryway/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "entryway" -version = "0.1.0" -edition = "2024" - -[dependencies] -atproto.workspace = true -router.workspace = true -http = "1.3.1" -serde.workspace = true -serde_json.workspace = true -tokio.workspace = true -tracing-subscriber.workspace = true -tracing.workspace = true diff --git a/entryway/src/main.rs b/entryway/src/main.rs deleted file mode 100644 index 63c6fc1..0000000 --- a/entryway/src/main.rs +++ /dev/null @@ -1,57 +0,0 @@ -use router::{ - Router, - xrpc::{ - XrpcEndpoint, - ProcedureInput, - Response, - error, - }, -}; -use serde::Deserialize; -use atproto::types::Nsid; -use http::status::StatusCode; -use tracing::{ - event, - instrument, - Level, -}; -use std::fmt::Debug; - -struct Config { - entryway_url: String, - entryway_did: String, - entryway_plc_rotation_key: String, - entryway_jwt_key_256_hex: String, -} - -#[tokio::main] -async fn main() { - let subscriber = tracing_subscriber::FmtSubscriber::new(); - let _ = tracing::subscriber::set_global_default(subscriber); - - let mut router = Router::new(); - let create_account_nsid: Nsid = "com.atproto.server.createAccount".parse::().expect("valid nsid"); - router = router.add_endpoint(XrpcEndpoint::not_implemented()); - router = router.add_endpoint(XrpcEndpoint::new_procedure(create_account_nsid, create_account)); - router.serve().await; -} - -#[derive(Deserialize, Debug)] -struct CreateAccountInput { - email: Option, - handle: String, - did: Option, - invite_code: Option, - verification_code: Option, - verification_phone: Option, - password: Option, - recovery_key: Option, - plc_op: Option, -} - -#[instrument] -async fn create_account(data: ProcedureInput) -> Response { - event!(Level::INFO, "In create_account"); - - error(StatusCode::OK, "error", "message") -} diff --git a/flake.lock b/flake.lock index c3be24b..69e701e 100644 --- a/flake.lock +++ b/flake.lock @@ -2,18 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1752436162, - "narHash": "sha256-Kt1UIPi7kZqkSc5HVj6UY5YLHHEzPBkgpNUByuyxtlw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "dfcd5b901dbab46c9c6e80b265648481aafb01f8", - "type": "github" + "lastModified": 1735563628, + "narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=", + "rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798", + "revCount": 637546, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.637546%2Brev-b134951a4c9f3c995fd7be05f3243f8ecd65d798/01941dc2-2ab2-7453-8ebd-88712e28efae/source.tar.gz" }, "original": { - "owner": "NixOS", - "ref": "nixos-25.05", - "repo": "nixpkgs", - "type": "github" + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.2405.%2A.tar.gz" } }, "nixpkgs_2": { @@ -43,11 +41,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1752547600, - "narHash": "sha256-0vUE42ji4mcCvQO8CI0Oy8LmC6u2G4qpYldZbZ26MLc=", + "lastModified": 1749695868, + "narHash": "sha256-debjTLOyqqsYOUuUGQsAHskFXH5+Kx2t3dOo/FCoNRA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "9127ca1f5a785b23a2fc1c74551a27d3e8b9a28b", + "rev": "55f914d5228b5c8120e9e0f9698ed5b7214d09cd", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 014140f..48d94bf 100644 --- a/flake.nix +++ b/flake.nix @@ -3,15 +3,13 @@ # Flake inputs inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2405.*.tar.gz"; rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix }; # Flake outputs outputs = { self, nixpkgs, rust-overlay }: let - pdsDirectory = "/home/pan/prog/atproto/appview"; - # Overlays enable you to customize the Nixpkgs attribute set overlays = [ # Makes a `rust-bin` attribute available in Nixpkgs @@ -35,88 +33,19 @@ forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f { pkgs = import nixpkgs { inherit overlays system; }; }); - - # Systemd service configuration - createSystemdService = pkgs: pdsDir: pkgs.writeTextFile { - name = "pds.service"; - text = '' - [Unit] - Description=Development Environment Service - After=network-online.target - Wants=network-online.target - - [Service] - Type=simple - ExecStart=${pkgs.pds}/bin/pds - WorkingDirectory=${pdsDir} - EnvironmentFile=${pdsDir}/.env - Environment=PDS_DATA_DIRECTORY=${pdsDir}/.pds-data - Environment=PDS_BLOBSTORE_DISK_LOCATION=${pdsDir}/.pds-data/blocks - ''; - }; - - # Scripts for managing the systemd service - createServiceScripts = pkgs: pdsDir: - let - serviceFile = createSystemdService pkgs pdsDir; - serviceName = "pds"; - in { - startScript = pkgs.writeShellScript "start-dev-service" '' - set -e - - # Create user systemd directory if it doesn't exist - mkdir -p ~/.config/systemd/user - - # Copy service file - cp -f ${serviceFile} ~/.config/systemd/user/${serviceName}.service - - # Reload systemd and start service - systemctl --user daemon-reload - systemctl --user start ${serviceName} - systemctl --user enable ${serviceName} - - systemctl --user status ${serviceName} --no-pager - ''; - - stopScript = pkgs.writeShellScript "stop-dev-service" '' - set -e - if systemctl --user is-enabled --quiet ${serviceName}; then - # Stop and disable service - systemctl --user stop ${serviceName} || true - systemctl --user disable ${serviceName} || true - - # Remove service file - rm -f ~/.config/systemd/user/${serviceName}.service - - # Reload systemd - systemctl --user daemon-reload - fi - ''; - }; in { # Development environment output - devShells = forAllSystems ({ pkgs }: - let - scripts = createServiceScripts pkgs pdsDirectory; - in { - default = pkgs.mkShell { - # The Nix packages provided in the environment - packages = (with pkgs; [ - # The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt, - # rustdoc, rustfmt, and other tools. - sqlx-cli - rustToolchain - ]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]); - - shellHook = pkgs.lib.optionalString pkgs.stdenv.isLinux '' - # Cleanup - ${scripts.stopScript} - - # Start the systemd service - ${scripts.startScript} - ''; - }; - }); + devShells = forAllSystems ({ pkgs }: { + default = pkgs.mkShell { + # The Nix packages provided in the environment + packages = (with pkgs; [ + # The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt, + # rustdoc, rustfmt, and other tools. + sqlx-cli + rustToolchain + ]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]); + }; + }); }; } diff --git a/router/Cargo.toml b/router/Cargo.toml deleted file mode 100644 index 2426dd6..0000000 --- a/router/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "router" -version = "0.1.0" -edition = "2024" - -[dependencies] -atproto.workspace = true -axum = { version = "0.8.3", features = ["json"] } -bon = "3.6.4" -http = "1.3.1" -serde.workspace = true -serde_json.workspace = true -tokio.workspace = true -tracing-subscriber.workspace = true -tracing.workspace = true diff --git a/router/src/lib.rs b/router/src/lib.rs deleted file mode 100644 index bddad49..0000000 --- a/router/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::xrpc::XrpcEndpoint; -use axum::Router as AxumRouter; -use core::net::SocketAddr; -use std::net::{IpAddr, Ipv4Addr}; -use tokio::net::TcpListener; - -pub mod xrpc; -pub mod wellknown; - -pub enum Error {} - -pub struct Router { - addr: SocketAddr, - router: AxumRouter, -} -impl Default for Router { - fn default() -> Self { - Self::new() - } -} -impl Router { - pub fn new() -> Self { - let router = AxumRouter::new(); - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 6702); - Router { router, addr } - } - - pub fn add_endpoint(mut self, endpoint: E) -> Self { - self.router = endpoint.add_to_router(self.router); - self - } - - pub async fn serve(self) { - let listener = TcpListener::bind(self.addr).await.unwrap(); - - axum::serve(listener, self.router).await.unwrap(); - } -} - -pub trait Endpoint { - fn add_to_router(self, router: AxumRouter) -> AxumRouter; -} diff --git a/router/src/wellknown.rs b/router/src/wellknown.rs deleted file mode 100644 index 394a794..0000000 --- a/router/src/wellknown.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::Endpoint; -use axum::{ - routing::method_routing::MethodRouter, - Router as axumRouter, -}; - -pub mod atproto; -pub mod oauth; - -trait WellKnownEndpoint { - fn get_known_route(&self) -> String; - fn get_resolver(self) -> MethodRouter; -} - -impl Endpoint for WK { - fn add_to_router(self, router: axumRouter) -> axumRouter { - router.route(&format!(".well-known/{}", self.get_known_route()), self.get_resolver()) - } -} diff --git a/router/src/wellknown/atproto.rs b/router/src/wellknown/atproto.rs deleted file mode 100644 index 0f99da0..0000000 --- a/router/src/wellknown/atproto.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod handle_resolution; diff --git a/router/src/wellknown/atproto/handle_resolution.rs b/router/src/wellknown/atproto/handle_resolution.rs deleted file mode 100644 index b3d75ce..0000000 --- a/router/src/wellknown/atproto/handle_resolution.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - wellknown::WellKnownEndpoint, - Error, -}; -use atproto::types::{Handle, Did}; -use axum::{ - routing::{ - method_routing::MethodRouter, - get, - }, - http::{ - StatusCode, - HeaderMap, - }, -}; - -pub struct HandleResolutionEndpoint { - resolver: MethodRouter, -} - -impl HandleResolutionEndpoint { - pub fn new


(handle_resolver: HR) -> Self where - HR: HandleResolver + Clone - { - HandleResolutionEndpoint { - resolver: get(async move | headers: HeaderMap | -> (StatusCode, String) { - let Some(Ok(hostname)) = headers.get("host").map(|header_value| { - header_value.to_str() - }) else { - return (StatusCode::INTERNAL_SERVER_ERROR, String::from("Internal Server Error")); - }; - let Ok(valid_handle) = hostname.parse::() else { - return (StatusCode::NOT_FOUND, String::from("User not found")); - }; - match handle_resolver.call(valid_handle) { - Ok(Some(did)) => (StatusCode::OK, did.to_string()), - Ok(None) => (StatusCode::NOT_FOUND, String::from("User not found")), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, String::from("Internal Server Error")), - } - }) - } - } -} - -pub trait HandleResolver: Send + Sync + 'static { - fn call(&self, handle: Handle) -> Result, Error>; -} - -impl WellKnownEndpoint for HandleResolutionEndpoint { - fn get_known_route(&self) -> String { String::from("atproto-did") } - fn get_resolver(self) -> MethodRouter { self.resolver } -} diff --git a/router/src/wellknown/oauth.rs b/router/src/wellknown/oauth.rs deleted file mode 100644 index 887d5fd..0000000 --- a/router/src/wellknown/oauth.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod authorization_server; diff --git a/router/src/wellknown/oauth/authorization_server.rs b/router/src/wellknown/oauth/authorization_server.rs deleted file mode 100644 index d2ae12f..0000000 --- a/router/src/wellknown/oauth/authorization_server.rs +++ /dev/null @@ -1,103 +0,0 @@ -use serde::de::{ - Error, - Unexpected, -}; -use serde_json::{ - json, - Result, - Value -}; -use bon::Builder; - -trait Metadata { - fn format_metadata(self, required: RequiredMetadata) -> Result; -} - -pub struct RequiredMetadata { - issuer: String, - authorization_endpoint: String, - token_endpoint: String, -} - -impl RequiredMetadata { - fn new( - issuer: String, - authorization_endpoint: String, - token_endpoint: String - ) -> Self { - RequiredMetadata { - issuer, authorization_endpoint, token_endpoint - } - } -} - -#[derive(Builder)] -struct AtprotoMetadata { - additional_response_types_supported: Option>, - additional_grant_types_supported: Option>, - additional_code_challenge_methods_supported: Option>, - additional_token_endpoint_auth_methods_supported: Option>, - additional_token_endpoint_auth_signing_alg_values_supported: Option>, - additional_scopes_supported: Option>, - pushed_authorization_request_endpoint: String, - additional_dpop_signing_alg_values_supported: Option>, -} - -impl AtprotoMetadata { - fn check_fields(&self) -> Result<()> { - // TODO: Issuer check (https scheme, no default port, no path segments - - if self.additional_token_endpoint_auth_signing_alg_values_supported - .as_ref() - .is_none_or(|vec| vec.iter().any(|s| s == "none")) { - return Err(Error::invalid_value( - Unexpected::Other("\"none\" in token_endpoint_auth_signing_alg_values_supported"), - &"\"none\" to be omitted from token_endpoint_auth_signing_alg_values_supported" - )); - } - - Ok(()) - } -} - -impl Metadata for AtprotoMetadata { - fn format_metadata(self, required: RequiredMetadata) -> Result { - self.check_fields()?; - Ok(json!({ - "issuer": required.issuer, - "authorization_endpoint": required.authorization_endpoint, - "token_endpoint": required.token_endpoint, - "response_types_supported": - self.additional_response_types_supported.unwrap_or_default() - .extend(["code".to_string()]), - "grant_types_supported": - self.additional_grant_types_supported.unwrap_or_default() - .extend([ - "authorization_code".to_string(), - "refresh_token".to_string() - ]), - "code_challenge_methods_supported": - self.additional_code_challenge_methods_supported.unwrap_or_default() - .extend(["S256".to_string()]), - "token_endpoint_auth_methods_supported": - self.additional_token_endpoint_auth_methods_supported.unwrap_or_default() - .extend([ - "none".to_string(), - "private_key_jwt".to_string() - ]), - "token_endpoint_auth_signing_alg_values_supported": - self.additional_token_endpoint_auth_signing_alg_values_supported.unwrap_or_default() - .extend(["ES256".to_string()]), - "scopes_supported": - self.additional_scopes_supported.unwrap_or_default() - .extend(["atproto".to_string()]), - "authorization_response_iss_parameter_supported": true, - "require_pushed_authorization_requests": true, - "pushed_authorization_request_endpoint": self.pushed_authorization_request_endpoint, - "dpop_signing_alg_values_supported": - self.additional_dpop_signing_alg_values_supported.unwrap_or_default() - .extend(["ES256".to_string()]), - "client_id_metadata_document_supported": true, - })) - } -}