summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2025-07-12 15:57:19 +0200
committerrtkay123 <dev@kanjala.com>2025-07-12 15:57:19 +0200
commit487ac435d7b687f071a0ed173d918fac480992b3 (patch)
tree3dd870414508cc437a4b070ba93ace7d3e2df5ad
parentba14505f39d8634921f260d715aa8e66f2a14406 (diff)
downloadsellershut-487ac435d7b687f071a0ed173d918fac480992b3.tar.bz2
sellershut-487ac435d7b687f071a0ed173d918fac480992b3.zip
feat: create user
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml3
-rw-r--r--src/entity.rs1
-rw-r--r--src/entity/user.rs109
-rw-r--r--src/main.rs8
-rw-r--r--src/server.rs6
-rw-r--r--src/server/routes.rs7
-rw-r--r--src/state.rs45
8 files changed, 173 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5ed399b..bf320d7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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]]
diff --git a/Cargo.toml b/Cargo.toml
index ee7a0bc..1bd7537 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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)
+ }
+}