From 4d4e3ed79444d0aa5dbe13db25c5d976db343898 Mon Sep 17 00:00:00 2001 From: aprilthepink Date: Thu, 18 Apr 2024 03:41:52 +0200 Subject: [PATCH] [feat] db code? --- src/activities/create_post.rs | 18 +++++------- src/database.rs | 29 +++++++++++++------ src/entities/post.rs | 10 +++++-- src/entities/user.rs | 10 +++++-- src/http.rs | 23 +++++++-------- src/main.rs | 19 ++++++------ src/objects/person.rs | 54 +++++++++++++++++------------------ src/objects/post.rs | 35 ++++++++++++----------- 8 files changed, 106 insertions(+), 92 deletions(-) diff --git a/src/activities/create_post.rs b/src/activities/create_post.rs index 74fd388..70de587 100644 --- a/src/activities/create_post.rs +++ b/src/activities/create_post.rs @@ -1,9 +1,5 @@ use crate::{ - database::DatabaseHandle, - error::Error, - objects::post::DbPost, - objects::{person::DbUser, post::Note}, - utils::generate_object_id, + database::StateHandle, entities::{post, user}, error::Error, objects::{person::DbUser, post::{DbPost, Note}}, utils::generate_object_id }; use activitypub_federation::{ activity_sending::SendActivityTask, @@ -19,7 +15,7 @@ use url::Url; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct CreatePost { - pub(crate) actor: ObjectId, + pub(crate) actor: ObjectId, #[serde(deserialize_with = "deserialize_one_or_many")] pub(crate) to: Vec, pub(crate) object: Note, @@ -29,7 +25,7 @@ pub struct CreatePost { } impl CreatePost { - pub async fn send(note: Note, inbox: Url, data: &Data) -> Result<(), Error> { + pub async fn send(note: Note, inbox: Url, data: &Data) -> Result<(), Error> { print!("Sending reply to {}", ¬e.attributed_to); let create = CreatePost { actor: note.attributed_to.clone(), @@ -40,7 +36,7 @@ impl CreatePost { }; let create_with_context = WithContext::new_default(create); let sends = - SendActivityTask::prepare(&create_with_context, &data.local_user(), vec![inbox], data) + SendActivityTask::prepare(&create_with_context, &data.local_user().await?, vec![inbox], data) .await?; for send in sends { send.sign_and_send(data).await?; @@ -51,7 +47,7 @@ impl CreatePost { #[async_trait::async_trait] impl ActivityHandler for CreatePost { - type DataType = DatabaseHandle; + type DataType = StateHandle; type Error = crate::error::Error; fn id(&self) -> &Url { @@ -63,12 +59,12 @@ impl ActivityHandler for CreatePost { } async fn verify(&self, data: &Data) -> Result<(), Self::Error> { - DbPost::verify(&self.object, &self.id, data).await?; + post::Model::verify(&self.object, &self.id, data).await?; Ok(()) } async fn receive(self, data: &Data) -> Result<(), Self::Error> { - DbPost::from_json(self.object, data).await?; + post::Model::from_json(self.object, data).await?; Ok(()) } } diff --git a/src/database.rs b/src/database.rs index 0e9ee7a..3ea3cc7 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,9 +1,20 @@ -use crate::{error::Error, objects::person::DbUser}; +use crate::{entities::user, error::Error, objects::person::DbUser}; use anyhow::anyhow; +use sea_orm::{DatabaseConnection, EntityTrait}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; +use super::entities::prelude::User; -pub type DatabaseHandle = Arc; +#[derive(Debug, Clone)] +pub struct Config {} + +#[derive(Debug, Clone)] +pub struct State { + pub database_connection: Arc, + pub config: Arc, +} + +pub type StateHandle = Arc; /// Our "database" which contains all known users (local and federated) #[derive(Debug)] @@ -11,15 +22,15 @@ pub struct Database { pub users: Mutex>, } -impl Database { - pub fn local_user(&self) -> DbUser { - let lock = self.users.lock().unwrap(); - lock.first().unwrap().clone() +impl State { + pub async fn local_user(&self) -> Result { + let user = User::find().one(self.database_connection.as_ref()).await?.unwrap(); + Ok(user.clone()) } - pub fn read_user(&self, name: &str) -> Result { - let db_user = self.local_user(); - if name == db_user.name { + pub async fn read_user(&self, name: &str) -> Result { + let db_user = self.local_user().await?; + if name == db_user.username { Ok(db_user) } else { Err(anyhow!("Invalid user {name}").into()) diff --git a/src/entities/post.rs b/src/entities/post.rs index 08445e7..97bbd23 100644 --- a/src/entities/post.rs +++ b/src/entities/post.rs @@ -1,5 +1,6 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 +use chrono::Utc; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -10,9 +11,12 @@ pub struct Model { pub title: Option, pub content: String, pub local: bool, - pub created_at: String, - pub updated_at: Option, - pub reblog_id: Option, + #[sea_orm(column_type = "Timestamp")] + pub created_at: chrono::DateTime, + #[sea_orm(column_type = "Timestamp")] + pub updated_at: Option>, + #[sea_orm(column_type = "Timestamp")] + pub reblog_id: Option>, pub content_type: String, pub visibility: String, pub reply_id: Option, diff --git a/src/entities/user.rs b/src/entities/user.rs index a613831..2575ef0 100644 --- a/src/entities/user.rs +++ b/src/entities/user.rs @@ -1,5 +1,6 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10 +use chrono::Utc; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -13,12 +14,15 @@ pub struct Model { pub url: String, pub public_key: String, pub private_key: Option, - pub last_refreshed_at: String, + #[sea_orm(column_type = "Timestamp")] + pub last_refreshed_at: chrono::DateTime, pub local: bool, pub follower_count: i32, pub following_count: i32, - pub created_at: String, - pub updated_at: Option, + #[sea_orm(column_type = "Timestamp")] + pub created_at: chrono::DateTime, + #[sea_orm(column_type = "Timestamp")] + pub updated_at: Option>, pub following: Option, pub followers: Option, pub inbox: String, diff --git a/src/http.rs b/src/http.rs index 8e64a91..13a7515 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,7 +1,5 @@ use crate::{ - database::DatabaseHandle, - error::Error, - objects::person::{DbUser, PersonAcceptedActivities}, + database::StateHandle, entities::user, error::Error, objects::person::{DbUser, PersonAcceptedActivities} }; use activitypub_federation::{ actix_web::{inbox::receive_activity, signing_actor}, @@ -15,8 +13,9 @@ use actix_web::{web, web::Bytes, App, HttpRequest, HttpResponse, HttpServer}; use anyhow::anyhow; use serde::Deserialize; use tracing::info; +use url::Url; -pub fn listen(config: &FederationConfig) -> Result<(), Error> { +pub fn listen(config: &FederationConfig) -> Result<(), Error> { let hostname = config.domain(); info!("Listening with actix-web on {hostname}"); let config = config.clone(); @@ -46,7 +45,7 @@ pub fn listen(config: &FederationConfig) -> Result<(), Error> { pub async fn http_get_user( request: HttpRequest, user_name: web::Path, - data: Data, + data: Data, ) -> Result { //let signed_by = signing_actor::(&request, None, &data).await?; // here, checks can be made on the actor or the domain to which @@ -56,8 +55,8 @@ pub async fn http_get_user( // signed_by.id() //); - let db_user = data.local_user(); - if user_name.into_inner() == db_user.name { + let db_user = data.local_user().await?; + if user_name.into_inner() == db_user.username { let json_user = db_user.into_json(&data).await?; Ok(HttpResponse::Ok() .content_type(FEDERATION_CONTENT_TYPE) @@ -71,9 +70,9 @@ pub async fn http_get_user( pub async fn http_post_user_inbox( request: HttpRequest, body: Bytes, - data: Data, + data: Data, ) -> Result { - receive_activity::, DbUser, DatabaseHandle>( + receive_activity::, user::Model, StateHandle>( request, body, &data, ) .await @@ -86,12 +85,12 @@ pub struct WebfingerQuery { pub async fn webfinger( query: web::Query, - data: Data, + data: Data, ) -> Result { let name = extract_webfinger_name(&query.resource, &data)?; - let db_user = data.read_user(name)?; + let db_user = data.read_user(name).await?; Ok(HttpResponse::Ok().json(build_webfinger_response( query.resource.clone(), - db_user.ap_id.into_inner(), + Url::parse(&db_user.id)?, ))) } diff --git a/src/main.rs b/src/main.rs index e324258..c0739df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use clap::Parser; use database::Database; use http::{http_get_user, http_post_user_inbox, webfinger}; use objects::person::DbUser; +use sea_orm::DatabaseConnection; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -15,6 +16,8 @@ use std::{ use tokio::signal; use tracing::info; +use crate::database::{Config, State}; + mod entities; mod activities; mod database; @@ -23,15 +26,6 @@ mod http; mod objects; mod utils; -#[derive(Debug, Clone)] -struct Config {} - -#[derive(Debug, Clone)] -struct State { - database: Arc, - config: Arc, -} - #[derive(Debug, Serialize, Deserialize)] struct Response { health: bool, @@ -63,6 +57,7 @@ async fn main() -> actix_web::Result<(), anyhow::Error> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let server_url = env::var("LISTEN").unwrap_or("127.0.0.1:8080".to_string()); + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let local_user = DbUser::new( env::var("FEDERATED_DOMAIN") @@ -78,16 +73,18 @@ async fn main() -> actix_web::Result<(), anyhow::Error> { users: Mutex::new(vec![local_user]), }); + let db = sea_orm::Database::connect(database_url).await?; + let config = Config {}; let state: State = State { - database: new_database, + database_connection: db.into(), config: Arc::new(config), }; let data = FederationConfig::builder() .domain(env::var("FEDERATED_DOMAIN").expect("FEDERATED_DOMAIN must be set")) - .app_data(state.clone().database) + .app_data(state.clone()) .build() .await?; diff --git a/src/objects/person.rs b/src/objects/person.rs index d9439ea..05a7a61 100644 --- a/src/objects/person.rs +++ b/src/objects/person.rs @@ -1,4 +1,4 @@ -use crate::{activities::create_post::CreatePost, database::DatabaseHandle, error::Error}; +use crate::{activities::create_post::CreatePost, database::{State, StateHandle}, entities::{self, user}, error::Error}; use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, @@ -7,7 +7,8 @@ use activitypub_federation::{ protocol::{public_key::PublicKey, verification::verify_domains_match}, traits::{ActivityHandler, Actor, Object}, }; -use chrono::{DateTime, Utc}; +use chrono::{prelude, DateTime, Utc}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use url::Url; @@ -15,7 +16,7 @@ use url::Url; #[derive(Debug, Clone)] pub struct DbUser { pub name: String, - pub ap_id: ObjectId, + pub ap_id: ObjectId, pub inbox: Url, // exists for all users (necessary to verify http signatures) pub public_key: String, @@ -58,14 +59,14 @@ pub struct Person { #[serde(rename = "type")] kind: PersonType, preferred_username: String, - id: ObjectId, + id: ObjectId, inbox: Url, public_key: PublicKey, } #[async_trait::async_trait] -impl Object for DbUser { - type DataType = DatabaseHandle; +impl Object for user::Model { + type DataType = StateHandle; type Kind = Person; type Error = Error; @@ -77,20 +78,19 @@ impl Object for DbUser { object_id: Url, data: &Data, ) -> Result, Self::Error> { - let users = data.users.lock().unwrap(); - let res = users - .clone() - .into_iter() - .find(|u| u.ap_id.inner() == &object_id); + let res = entities::prelude::User::find() + .filter(entities::user::Column::Id.eq(object_id.as_str())) + .one(data.database_connection.as_ref()) + .await?; Ok(res) } async fn into_json(self, _data: &Data) -> Result { Ok(Person { - preferred_username: self.name.clone(), + preferred_username: self.username.clone(), kind: Default::default(), - id: self.ap_id.clone(), - inbox: self.inbox.clone(), + id: Url::parse(&self.id).unwrap().into(), + inbox: Url::parse(&self.inbox).unwrap(), public_key: self.public_key(), }) } @@ -108,22 +108,22 @@ impl Object for DbUser { json: Self::Kind, _data: &Data, ) -> Result { - Ok(DbUser { - name: json.preferred_username, - ap_id: json.id, - inbox: json.inbox, - public_key: json.public_key.public_key_pem, - private_key: None, - last_refreshed_at: Utc::now(), - followers: vec![], - local: false, - }) + let model = user::ActiveModel { + id: Set(json.id.to_string()), + username: Set(json.preferred_username), + inbox: Set(json.inbox.to_string()), + public_key: Set(json.public_key.public_key_pem), + local: Set(false), + ..Default::default() + }; + let model = model.insert(_data.database_connection.as_ref()).await?; + Ok(model) } } -impl Actor for DbUser { +impl Actor for user::Model { fn id(&self) -> Url { - self.ap_id.inner().clone() + Url::parse(&self.id).unwrap() } fn public_key_pem(&self) -> &str { @@ -135,6 +135,6 @@ impl Actor for DbUser { } fn inbox(&self) -> Url { - self.inbox.clone() + Url::parse(&self.inbox).unwrap() } } diff --git a/src/objects/post.rs b/src/objects/post.rs index 7ac453c..850f938 100644 --- a/src/objects/post.rs +++ b/src/objects/post.rs @@ -1,6 +1,5 @@ use crate::{ - activities::create_post::CreatePost, database::DatabaseHandle, error::Error, - objects::person::DbUser, utils::generate_object_id, + activities::create_post::CreatePost, database::StateHandle, entities::{post, user}, error::Error, objects::person::DbUser, utils::generate_object_id }; use activitypub_federation::{ config::Data, @@ -10,14 +9,15 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use activitystreams_kinds::link::MentionType; +use sea_orm::{ActiveModelTrait, Set}; use serde::{Deserialize, Serialize}; use url::Url; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DbPost { pub text: String, - pub ap_id: ObjectId, - pub creator: ObjectId, + pub ap_id: ObjectId, + pub creator: ObjectId, pub local: bool, } @@ -26,12 +26,12 @@ pub struct DbPost { pub struct Note { #[serde(rename = "type")] kind: NoteType, - id: ObjectId, - pub(crate) attributed_to: ObjectId, + id: ObjectId, + pub(crate) attributed_to: ObjectId, #[serde(deserialize_with = "deserialize_one_or_many")] pub(crate) to: Vec, content: String, - in_reply_to: Option>, + in_reply_to: Option>, tag: Vec, } @@ -43,8 +43,8 @@ pub struct Mention { } #[async_trait::async_trait] -impl Object for DbPost { - type DataType = DatabaseHandle; +impl Object for post::Model { + type DataType = StateHandle; type Kind = Note; type Error = Error; @@ -74,21 +74,24 @@ impl Object for DbPost { &json.content, &json.id ); let creator = json.attributed_to.dereference(data).await?; - let post = DbPost { - text: json.content, - ap_id: json.id.clone(), - creator: json.attributed_to.clone(), - local: false, + let post: post::ActiveModel = post::ActiveModel { + content: Set(json.content.clone()), + id: Set(json.id.to_string()), + creator: Set(creator.id.to_string()), + local: Set(false), + ..Default::default() }; + let post = post.insert(data.app_data().database_connection.clone().as_ref()) + .await?; let mention = Mention { - href: creator.ap_id.clone().into_inner(), + href: Url::parse(&creator.id)?, kind: Default::default(), }; let note = Note { kind: Default::default(), id: generate_object_id(data.domain())?.into(), - attributed_to: data.local_user().ap_id, + attributed_to: Url::parse(&data.local_user().await?.id).unwrap().into(), to: vec![public()], content: format!("Hello {}", creator.name), in_reply_to: Some(json.id.clone()),