diff options
author | rtkay123 <dev@kanjala.com> | 2025-07-12 15:57:19 +0200 |
---|---|---|
committer | rtkay123 <dev@kanjala.com> | 2025-07-12 15:57:19 +0200 |
commit | 487ac435d7b687f071a0ed173d918fac480992b3 (patch) | |
tree | 3dd870414508cc437a4b070ba93ace7d3e2df5ad | |
parent | ba14505f39d8634921f260d715aa8e66f2a14406 (diff) | |
download | sellershut-487ac435d7b687f071a0ed173d918fac480992b3.tar.bz2 sellershut-487ac435d7b687f071a0ed173d918fac480992b3.zip |
feat: create user
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/entity.rs | 1 | ||||
-rw-r--r-- | src/entity/user.rs | 109 | ||||
-rw-r--r-- | src/main.rs | 8 | ||||
-rw-r--r-- | src/server.rs | 6 | ||||
-rw-r--r-- | src/server/routes.rs | 7 | ||||
-rw-r--r-- | src/state.rs | 45 |
8 files changed, 173 insertions, 9 deletions
@@ -1721,12 +1721,15 @@ version = "0.1.0" dependencies = [ "activitypub_federation", "anyhow", + "async-trait", "axum", + "serde", "stack-up", "tokio", "tower", "tower-http", "tracing", + "url", ] [[package]] @@ -10,10 +10,13 @@ description = "A federated marketplace platform" [dependencies] activitypub_federation = { version = "0.7.0-beta.5", default-features = false, features = ["axum"] } anyhow = "1.0.98" +async-trait = "0.1.88" axum = { version = "0.8.4", features = ["macros"] } +serde = { version = "1.0.219", features = ["derive"] } tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread", "signal"] } tower-http = { version = "0.6.6", features = ["trace"] } tracing = "0.1.41" +url = "2.5.4" [dependencies.stack-up] git = "https://github.com/rtkay123/stack-up.git" diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/entity.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/entity/user.rs b/src/entity/user.rs new file mode 100644 index 0000000..af27ea2 --- /dev/null +++ b/src/entity/user.rs @@ -0,0 +1,109 @@ +use activitypub_federation::{ + config::Data, + fetch::object_id::ObjectId, + http_signatures::generate_actor_keypair, + traits::{Actor, Object}, +}; +use async_trait::async_trait; +use serde::Deserialize; +use tracing::trace; +use url::Url; + +use crate::{error::AppError, state::AppHandle}; + +#[derive(PartialEq, Clone, Debug)] +pub(crate) struct LocalUser { + pub username: String, + pub ap_id: ObjectId<LocalUser>, + pub private_key: Option<String>, + pub public_key: String, + pub inbox: Url, +} + +impl LocalUser { + pub fn new(username: &str) -> Result<Self, AppError> { + trace!("creating a new user"); + let keys = generate_actor_keypair()?; + let stub = &format!("http://localhost/users/{username}"); + + Ok(Self { + username: username.to_owned(), + ap_id: Url::parse(stub)?.into(), + private_key: Some(keys.private_key), + public_key: keys.public_key, + inbox: Url::parse(&format!("{stub}/inbox"))?, + }) + } +} + +#[derive(Deserialize)] +pub struct User {} + +#[async_trait] +impl Object for LocalUser { + #[doc = " App data type passed to handlers. Must be identical to"] + #[doc = " [crate::config::FederationConfigBuilder::app_data] type."] + type DataType = AppHandle; + + #[doc = " The type of protocol struct which gets sent over network to federate this database struct."] + type Kind = User; + + #[doc = " Error type returned by handler methods"] + type Error = AppError; + + #[doc = " `id` field of the object"] + fn id(&self) -> &Url { + self.ap_id.inner() + } + + #[doc = " Try to read the object with given `id` from local database."] + #[doc = " Should return `Ok(None)` if not found."] + async fn read_from_id( + object_id: Url, + data: &Data<Self::DataType>, + ) -> Result<Option<Self>, Self::Error> { + todo!() + } + + #[doc = " Convert database type to Activitypub type."] + #[doc = " Called when a local object gets fetched by another instance over HTTP, or when an object"] + #[doc = " gets sent in an activity."] + async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { + todo!() + } + + #[doc = " Verifies that the received object is valid."] + #[doc = " You should check here that the domain of id matches `expected_domain`. Additionally you"] + #[doc = " should perform any application specific checks."] + #[doc = " It is necessary to use a separate method for this, because it might be used for activities"] + #[doc = " like `Delete/Note`, which shouldn\'t perform any database write for the inner `Note`."] + async fn verify( + json: &Self::Kind, + expected_domain: &Url, + data: &Data<Self::DataType>, + ) -> Result<(), Self::Error> { + todo!() + } + + #[doc = " Convert object from ActivityPub type to database type."] + #[doc = " Called when an object is received from HTTP fetch or as part of an activity. This method"] + #[doc = " should write the received object to database. Note that there is no distinction between"] + #[doc = " create and update, so an `upsert` operation should be used."] + async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> { + todo!() + } +} + +impl Actor for LocalUser { + fn public_key_pem(&self) -> &str { + &self.public_key + } + + fn private_key_pem(&self) -> Option<String> { + self.private_key.clone() + } + + fn inbox(&self) -> Url { + self.inbox.clone() + } +} diff --git a/src/main.rs b/src/main.rs index 9a73196..1e5b259 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,22 @@ +mod entity; mod error; mod server; mod state; use stack_up::{Monitoring, tracing::Tracing}; -use crate::error::AppError; +use crate::{error::AppError, state::AppState}; #[tokio::main] async fn main() -> Result<(), AppError> { let _tracing = Tracing::builder().build(&Monitoring { log_level: "trace".into(), }); + + let state = AppState::new().await?; let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?; tracing::debug!("listening on {}", listener.local_addr()?); - axum::serve(listener, server::router()).await?; + + axum::serve(listener, server::router(state)).await?; Ok(()) } diff --git a/src/server.rs b/src/server.rs index b639964..c6ce02c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,12 +1,14 @@ +use activitypub_federation::config::FederationConfig; use axum::{Router, routing::get}; use tower_http::trace::TraceLayer; -use crate::server::routes::health_check; +use crate::{server::routes::health_check, state::AppHandle}; pub mod routes; -pub fn router() -> Router { +pub fn router(state: FederationConfig<AppHandle>) -> Router { Router::new() .route("/", get(health_check)) .layer(TraceLayer::new_for_http()) + .with_state(state) } diff --git a/src/server/routes.rs b/src/server/routes.rs index aa8fa92..5814e0e 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -15,11 +15,12 @@ mod tests { }; use tower::ServiceExt; - use crate::server; + use crate::{server, state::AppState}; #[tokio::test] - async fn hello_world() { - let app = server::router(); + async fn health_check() { + let state = AppState::new().await.unwrap(); + let app = server::router(state); let response = app .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) diff --git a/src/state.rs b/src/state.rs index 69c6208..64c2e7c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,44 @@ -pub struct AppState {} +use std::{ + ops::Deref, + sync::{Arc, RwLock}, +}; -impl AppState {} +use activitypub_federation::config::FederationConfig; + +use crate::{entity::user::LocalUser, error::AppError}; + +#[derive(Clone)] +pub struct AppHandle(pub Arc<AppState>); + +impl Deref for AppHandle { + type Target = Arc<AppState>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct AppState { + users: RwLock<Vec<LocalUser>>, +} + +impl AppState { + pub async fn new() -> Result<FederationConfig<AppHandle>, AppError> { + let user = LocalUser::new("sellershut")?; + let domain = "localhost"; + + let config = FederationConfig::builder() + .domain(domain) + .signed_fetch_actor(&user) + .app_data(AppHandle(Arc::new(Self { + users: RwLock::new(vec![user]), + }))) + // .url_verifier(Box::new(MyUrlVerifier())) + // TODO: could change this to env variable? + .debug(cfg!(debug_assertions)) + .build() + .await?; + + Ok(config) + } +} |