activitypub/src/main.rs

210 lines
6.6 KiB
Rust
Raw Normal View History

2024-04-18 04:03:52 +02:00
use activitypub_federation::{
2024-05-04 17:54:11 +02:00
config::{Data, FederationConfig, FederationMiddleware}, fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, http_signatures::generate_actor_keypair, traits::Actor
2024-04-18 04:03:52 +02:00
};
2024-05-04 17:54:11 +02:00
use activitystreams_kinds::public;
use actix_web::{get, http::KeepAlive, middleware, post, web, App, Error, HttpResponse, HttpServer};
2024-04-09 19:48:18 +02:00
use actix_web_prom::PrometheusMetricsBuilder;
2024-05-04 17:54:11 +02:00
use async_once::AsyncOnce;
2024-04-18 04:03:52 +02:00
use chrono::{DateTime, Utc};
2024-04-09 19:48:18 +02:00
use clap::Parser;
use database::Database;
2024-05-04 17:54:11 +02:00
use entities::post;
2024-04-09 19:48:18 +02:00
use http::{http_get_user, http_post_user_inbox, webfinger};
use objects::person::DbUser;
2024-04-18 04:03:52 +02:00
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
2024-01-26 21:01:43 +01:00
use serde::{Deserialize, Serialize};
2024-04-09 19:48:18 +02:00
use std::{
2024-04-09 19:55:07 +02:00
collections::HashMap,
env,
net::ToSocketAddrs,
2024-05-04 17:54:11 +02:00
sync::{Arc, Mutex, OnceLock},
2024-04-09 19:48:18 +02:00
};
2024-04-09 19:55:07 +02:00
use tokio::signal;
use tracing::{info, instrument::WithSubscriber};
2024-04-18 04:03:52 +02:00
use url::Url;
2024-04-09 19:48:18 +02:00
2024-05-04 17:54:11 +02:00
use crate::{activities::create_post::CreatePost, database::{Config, State}, objects::post::{Mention, Note}};
use crate::entities::user;
use crate::utils::generate_object_id;
use lazy_static::lazy_static;
2024-04-18 03:41:52 +02:00
2024-04-09 19:48:18 +02:00
mod activities;
2024-04-09 19:55:07 +02:00
mod database;
2024-04-18 04:03:52 +02:00
mod entities;
2024-04-09 19:48:18 +02:00
mod error;
mod http;
2024-04-09 19:55:07 +02:00
mod objects;
mod utils;
2024-01-26 21:01:43 +01:00
#[derive(Debug, Serialize, Deserialize)]
struct Response {
health: bool,
}
2024-04-09 19:48:18 +02:00
#[derive(Parser, Debug)]
#[clap(author = "April John", version, about)]
/// Application configuration
struct Args {
/// whether to be verbose
#[arg(short = 'v')]
verbose: bool,
/// optional parse arg for config file
#[arg()]
config_file: Option<String>,
}
2024-01-26 21:01:43 +01:00
#[get("/")]
async fn index(_: web::Data<State>) -> actix_web::Result<HttpResponse, Error> {
Ok(HttpResponse::Ok().json(Response { health: true }))
}
2024-05-04 17:54:11 +02:00
#[post("/test/postmanually/{user}/{post}")]
async fn post_manually(
path: web::Path<(String, String)>,
state: web::Data<State>,
) -> actix_web::Result<HttpResponse, error::Error> {
let local_user = state.local_user().await?;
let data = FEDERATION_CONFIG.get().unwrap();
let creator = webfinger_resolve_actor::<State, user::Model>(path.0.as_str(), &data.to_request_data()).await?;
let mention = Mention {
href: Url::parse(&creator.id)?,
kind: Default::default(),
};
let id: ObjectId<post::Model> = generate_object_id(data.domain())?.into();
let note = Note {
kind: Default::default(),
id,
sensitive: false,
attributed_to: Url::parse(&data.local_user().await?.id).unwrap().into(),
to: vec![public()],
content: format!("Hello {}", creator.name),
tag: vec![mention],
in_reply_to: None,
};
CreatePost::send(note, creator.shared_inbox_or_inbox(), &data.to_request_data()).await?;
Ok(HttpResponse::Ok().json(Response { health: true }))
}
const DOMAIN_DEF: &str = "example.com";
2024-04-09 19:48:18 +02:00
const LOCAL_USER_NAME: &str = "example";
2024-05-04 17:54:11 +02:00
lazy_static!{
static ref SERVER_URL: String = env::var("LISTEN").unwrap_or("127.0.0.1:8080".to_string());
static ref DATABASE_URL: String = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
static ref USERNAME: String = env::var("LOCAL_USER_NAME").unwrap_or(LOCAL_USER_NAME.to_string());
static ref DOMAIN: String = env::var("FEDERATED_DOMAIN").unwrap_or(DOMAIN_DEF.to_string());
}
static DB: OnceLock<DatabaseConnection> = OnceLock::new();
static FEDERATION_CONFIG: OnceLock<FederationConfig<State>> = OnceLock::new();
2024-01-26 21:01:43 +01:00
#[actix_web::main]
2024-04-09 19:48:18 +02:00
async fn main() -> actix_web::Result<(), anyhow::Error> {
2024-01-26 21:01:43 +01:00
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
2024-05-04 17:54:11 +02:00
let ap_id = Url::parse(&format!("https://{}/{}", DOMAIN.to_string(), &USERNAME.to_string()))?;
let inbox = Url::parse(&format!("https://{}/{}/inbox", DOMAIN.to_string(), &USERNAME.to_string()))?;
2024-04-18 04:03:52 +02:00
let keypair = generate_actor_keypair()?;
let user = entities::user::ActiveModel {
2024-04-18 19:05:22 +02:00
id: Set(ap_id.clone().into()),
2024-05-04 17:54:11 +02:00
username: Set(USERNAME.to_string()),
2024-04-18 19:01:14 +02:00
name: Set("Test account <3".to_string()),
2024-04-18 04:03:52 +02:00
inbox: Set(inbox.to_string()),
public_key: Set(keypair.public_key.clone()),
private_key: Set(Some(keypair.private_key.clone())),
last_refreshed_at: Set(Utc::now()),
follower_count: Set(0),
following_count: Set(0),
2024-04-18 19:05:22 +02:00
url: Set(ap_id.to_string()),
2024-04-18 04:03:52 +02:00
local: Set(true),
created_at: Set(Utc::now()),
2024-04-18 04:03:52 +02:00
..Default::default()
};
2024-01-26 21:01:43 +01:00
2024-05-04 17:54:11 +02:00
let db = sea_orm::Database::connect(DATABASE_URL.to_string()).await?;
2024-04-18 03:41:52 +02:00
2024-04-18 18:30:15 +02:00
info!("Connected to database: {:?}", db);
2024-05-04 17:54:11 +02:00
DB.set(db).expect("We were not able to save the DB conn into memory");
let db = DB.get().unwrap();
let user = user.insert(db).await;
2024-04-18 04:03:52 +02:00
2024-04-18 18:26:41 +02:00
if let Err(err) = user {
eprintln!("Error inserting user: {:?}", err);
} else {
info!("User inserted: {:?}", user.unwrap());
}
2024-04-16 19:53:24 +02:00
let state: State = State {
2024-05-04 17:54:11 +02:00
database_connection: Arc::new(db.clone()),
2024-04-16 19:53:24 +02:00
};
2024-01-26 21:01:43 +01:00
2024-04-09 19:48:18 +02:00
let data = FederationConfig::builder()
.domain(env::var("FEDERATED_DOMAIN").expect("FEDERATED_DOMAIN must be set"))
2024-04-18 03:41:52 +02:00
.app_data(state.clone())
2024-04-18 04:16:33 +02:00
.http_signature_compat(true)
.signed_fetch_actor(&state.local_user().await.unwrap())
2024-04-09 19:55:07 +02:00
.build()
.await?;
2024-04-09 19:48:18 +02:00
let mut labels = HashMap::new();
2024-04-09 19:55:07 +02:00
labels.insert(
"domain".to_string(),
env::var("FEDERATED_DOMAIN")
.expect("FEDERATED_DOMAIN must be set")
.to_string(),
);
labels.insert(
"name".to_string(),
env::var("LOCAL_USER_NAME")
.expect("LOCAL_USER_NAME must be set")
.to_string(),
);
2024-04-09 19:48:18 +02:00
let prometheus = PrometheusMetricsBuilder::new("api")
.endpoint("/metrics")
.const_labels(labels)
.build()
.unwrap();
let http_server = HttpServer::new(move || {
2024-01-26 21:01:43 +01:00
App::new()
.app_data(web::Data::new(state.clone()))
.wrap(middleware::Logger::default()) // enable logger
2024-04-09 19:48:18 +02:00
.wrap(prometheus.clone())
.wrap(FederationMiddleware::new(data.clone()))
2024-05-04 17:54:11 +02:00
.service(post_manually)
2024-04-09 19:48:18 +02:00
.route("/{user}", web::get().to(http_get_user))
.route("/{user}/inbox", web::post().to(http_post_user_inbox))
.route("/.well-known/webfinger", web::get().to(webfinger))
2024-01-26 21:01:43 +01:00
.service(index)
})
2024-05-04 17:54:11 +02:00
.bind(SERVER_URL.to_string())?
2024-04-09 19:48:18 +02:00
.workers(num_cpus::get())
.shutdown_timeout(20)
.keep_alive(KeepAlive::Os)
.run();
tokio::spawn(http_server);
match signal::ctrl_c().await {
2024-04-09 19:55:07 +02:00
Ok(()) => {}
2024-04-09 19:48:18 +02:00
Err(err) => {
eprintln!("Unable to listen for shutdown signal: {}", err);
// we also shut down in case of error
2024-04-09 19:55:07 +02:00
}
2024-04-09 19:48:18 +02:00
}
2024-01-26 21:01:43 +01:00
2024-04-16 19:53:24 +02:00
info!("Main thread shutdown..");
2024-04-15 01:07:15 +02:00
2024-01-26 21:01:43 +01:00
Ok(())
}