whatamidoing
This commit is contained in:
		
						commit
						b7369e2cc0
					
				
					 22 changed files with 2913 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								.env
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.env
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| DATABASE_URL=postgres://ap_actix:ap_actix@localhost:5432/ap_actix | ||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| /target | ||||
							
								
								
									
										2187
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2187
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										23
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| [package] | ||||
| name = "ap-actix" | ||||
| version = "0.1.0" | ||||
| authors = ["asonix <asonix@asonix.dog>"] | ||||
| edition = "2018" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| 
 | ||||
| [dependencies] | ||||
| anyhow = "1.0" | ||||
| actix = "0.10.0-alpha.2" | ||||
| actix-web = { version = "3.0.0-alpha.1", features = ["openssl"] } | ||||
| actix-rt = "1.0.0" | ||||
| activitystreams = "0.5.0-alpha.5" | ||||
| bb8-postgres = "0.4.0" | ||||
| dotenv = "0.15.0" | ||||
| futures = "0.3.4" | ||||
| log = "0.4" | ||||
| pretty_env_logger = "0.4.0" | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| thiserror = "1.0" | ||||
| tokio = { version = "0.2.13", features = ["sync"] } | ||||
							
								
								
									
										5
									
								
								diesel.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								diesel.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| # For documentation on how to configure this file, | ||||
| # see diesel.rs/guides/configuring-diesel-cli | ||||
| 
 | ||||
| [print_schema] | ||||
| file = "src/schema.rs" | ||||
							
								
								
									
										0
									
								
								migrations/.gitkeep
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								migrations/.gitkeep
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								migrations/00000000000000_diesel_initial_setup/down.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								migrations/00000000000000_diesel_initial_setup/down.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| -- This file was automatically created by Diesel to setup helper functions | ||||
| -- and other internal bookkeeping. This file is safe to edit, any future | ||||
| -- changes will be added to existing projects as new migrations. | ||||
| 
 | ||||
| DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); | ||||
| DROP FUNCTION IF EXISTS diesel_set_updated_at(); | ||||
							
								
								
									
										36
									
								
								migrations/00000000000000_diesel_initial_setup/up.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								migrations/00000000000000_diesel_initial_setup/up.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| -- This file was automatically created by Diesel to setup helper functions | ||||
| -- and other internal bookkeeping. This file is safe to edit, any future | ||||
| -- changes will be added to existing projects as new migrations. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| -- Sets up a trigger for the given table to automatically set a column called | ||||
| -- `updated_at` whenever the row is modified (unless `updated_at` was included | ||||
| -- in the modified columns) | ||||
| -- | ||||
| -- # Example | ||||
| -- | ||||
| -- ```sql | ||||
| -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); | ||||
| -- | ||||
| -- SELECT diesel_manage_updated_at('users'); | ||||
| -- ``` | ||||
| CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ | ||||
| BEGIN | ||||
|     EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s | ||||
|                     FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); | ||||
| END; | ||||
| $$ LANGUAGE plpgsql; | ||||
| 
 | ||||
| CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ | ||||
| BEGIN | ||||
|     IF ( | ||||
|         NEW IS DISTINCT FROM OLD AND | ||||
|         NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at | ||||
|     ) THEN | ||||
|         NEW.updated_at := current_timestamp; | ||||
|     END IF; | ||||
|     RETURN NEW; | ||||
| END; | ||||
| $$ LANGUAGE plpgsql; | ||||
							
								
								
									
										3
									
								
								migrations/2020-03-14-211045_create_listeners/down.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								migrations/2020-03-14-211045_create_listeners/down.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| -- This file should undo anything in `up.sql` | ||||
| DROP INDEX listeners_actor_id_index; | ||||
| DROP TABLE listeners; | ||||
							
								
								
									
										11
									
								
								migrations/2020-03-14-211045_create_listeners/up.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								migrations/2020-03-14-211045_create_listeners/up.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| -- Your SQL goes here | ||||
| CREATE TABLE listeners ( | ||||
|     id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | ||||
|     actor_id TEXT UNIQUE NOT NULL, | ||||
|     created_at TIMESTAMP NOT NULL, | ||||
|     updated_at TIMESTAMP NOT NULL | ||||
| ); | ||||
| 
 | ||||
| CREATE INDEX listeners_actor_id_index ON listeners(actor_id); | ||||
| 
 | ||||
| SELECT diesel_manage_updated_at('listeners'); | ||||
							
								
								
									
										3
									
								
								migrations/2020-03-14-213217_create_blocks/down.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								migrations/2020-03-14-213217_create_blocks/down.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| -- This file should undo anything in `up.sql` | ||||
| DROP INDEX blocks_actor_id_index; | ||||
| DROP TABLE blocks; | ||||
							
								
								
									
										11
									
								
								migrations/2020-03-14-213217_create_blocks/up.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								migrations/2020-03-14-213217_create_blocks/up.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| -- Your SQL goes here | ||||
| CREATE TABLE blocks ( | ||||
|     id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | ||||
|     actor_id TEXT UNIQUE NOT NULL, | ||||
|     created_at TIMESTAMP NOT NULL, | ||||
|     updated_at TIMESTAMP NOT NULL | ||||
| ); | ||||
| 
 | ||||
| CREATE INDEX blocks_actor_id_index ON blocks(actor_id); | ||||
| 
 | ||||
| SELECT diesel_manage_updated_at('blocks'); | ||||
							
								
								
									
										3
									
								
								migrations/2020-03-14-213511_create_whitelists/down.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								migrations/2020-03-14-213511_create_whitelists/down.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| -- This file should undo anything in `up.sql` | ||||
| DROP INDEX whitelists_actor_id_index; | ||||
| DROP TABLE whitelists; | ||||
							
								
								
									
										11
									
								
								migrations/2020-03-14-213511_create_whitelists/up.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								migrations/2020-03-14-213511_create_whitelists/up.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| -- Your SQL goes here | ||||
| CREATE TABLE whitelists ( | ||||
|     id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | ||||
|     actor_id TEXT UNIQUE NOT NULL, | ||||
|     created_at TIMESTAMP NOT NULL, | ||||
|     updated_at TIMESTAMP NOT NULL | ||||
| ); | ||||
| 
 | ||||
| CREATE INDEX whitelists_actor_id_index ON whitelists(actor_id); | ||||
| 
 | ||||
| SELECT diesel_manage_updated_at('whitelists'); | ||||
							
								
								
									
										86
									
								
								src/apub.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/apub.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| use activitystreams::{ | ||||
|     object::{Object, ObjectBox}, | ||||
|     primitives::XsdAnyUri, | ||||
|     PropRefs, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PropRefs)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| #[prop_refs(Object)] | ||||
| pub struct AnyExistingObject { | ||||
|     pub id: XsdAnyUri, | ||||
| 
 | ||||
|     #[serde(rename = "type")] | ||||
|     pub kind: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "PascalCase")] | ||||
| pub enum ValidTypes { | ||||
|     Announce, | ||||
|     Create, | ||||
|     Delete, | ||||
|     Follow, | ||||
|     Undo, | ||||
| } | ||||
| 
 | ||||
| impl Default for ValidTypes { | ||||
|     fn default() -> Self { | ||||
|         ValidTypes::Create | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(untagged)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub enum ValidObjects { | ||||
|     Id(XsdAnyUri), | ||||
|     Object(AnyExistingObject), | ||||
| } | ||||
| 
 | ||||
| impl Default for ValidObjects { | ||||
|     fn default() -> Self { | ||||
|         ValidObjects::Id(Default::default()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Default, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct AcceptedObjects { | ||||
|     pub id: XsdAnyUri, | ||||
| 
 | ||||
|     #[serde(rename = "type")] | ||||
|     pub kind: ValidTypes, | ||||
| 
 | ||||
|     pub actor: XsdAnyUri, | ||||
| 
 | ||||
|     pub object: ValidObjects, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct AcceptedActors { | ||||
|     pub id: XsdAnyUri, | ||||
| 
 | ||||
|     #[serde(rename = "type")] | ||||
|     pub kind: String, | ||||
| 
 | ||||
|     pub inbox: XsdAnyUri, | ||||
| 
 | ||||
|     pub endpoints: Endpoints, | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct Endpoints { | ||||
|     shared_inbox: Option<XsdAnyUri>, | ||||
| } | ||||
| 
 | ||||
| impl ValidObjects { | ||||
|     pub fn id(&self) -> &XsdAnyUri { | ||||
|         match self { | ||||
|             ValidObjects::Id(ref id) => id, | ||||
|             ValidObjects::Object(ref obj) => &obj.id, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										100
									
								
								src/cache.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/cache.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,100 @@ | |||
| use std::collections::{BTreeMap, HashMap, LinkedList}; | ||||
| 
 | ||||
| pub struct WeightedCache<K, V> | ||||
| where | ||||
|     K: std::hash::Hash + Eq + Clone, | ||||
| { | ||||
|     size: usize, | ||||
|     capacity: usize, | ||||
|     forward: HashMap<K, (V, Count)>, | ||||
|     backward: BTreeMap<Count, LinkedList<K>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] | ||||
| pub struct Count(usize); | ||||
| 
 | ||||
| impl<K, V> WeightedCache<K, V> | ||||
| where | ||||
|     K: std::hash::Hash + Eq + Clone, | ||||
| { | ||||
|     /// Create a new Weighted Cache
 | ||||
|     ///
 | ||||
|     /// panics if capacity is 0
 | ||||
|     pub fn new(capacity: usize) -> Self { | ||||
|         if capacity == 0 { | ||||
|             panic!("Cache Capacity must be > 0"); | ||||
|         } | ||||
| 
 | ||||
|         WeightedCache { | ||||
|             size: 0, | ||||
|             capacity, | ||||
|             forward: HashMap::new(), | ||||
|             backward: BTreeMap::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Gets a value from the weighted cache
 | ||||
|     pub fn get(&mut self, key: K) -> Option<&V> { | ||||
|         let (value, count) = self.forward.get_mut(&key)?; | ||||
| 
 | ||||
|         if let Some(v) = self.backward.get_mut(count) { | ||||
|             v.drain_filter(|item| item == &key); | ||||
|         } | ||||
| 
 | ||||
|         count.0 += 1; | ||||
| 
 | ||||
|         let entry = self.backward.entry(*count).or_insert(LinkedList::new()); | ||||
|         entry.push_back(key); | ||||
| 
 | ||||
|         Some(&*value) | ||||
|     } | ||||
| 
 | ||||
|     /// set a value in the weighted cache
 | ||||
|     pub fn insert(&mut self, key: K, value: V) -> Option<V> { | ||||
|         if self.forward.contains_key(&key) { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let ret = if self.size >= self.capacity { | ||||
|             self.remove_least() | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
| 
 | ||||
|         let count = Count(1); | ||||
|         self.forward.insert(key.clone(), (value, count)); | ||||
|         let entry = self.backward.entry(count).or_insert(LinkedList::new()); | ||||
| 
 | ||||
|         entry.push_back(key); | ||||
|         self.size += 1; | ||||
| 
 | ||||
|         ret | ||||
|     } | ||||
| 
 | ||||
|     fn remove_least(&mut self) -> Option<V> { | ||||
|         let items = self.backward.values_mut().next()?; | ||||
| 
 | ||||
|         let oldest = items.pop_front()?; | ||||
|         let length = items.len(); | ||||
|         drop(items); | ||||
| 
 | ||||
|         let (item, count) = self.forward.remove(&oldest)?; | ||||
| 
 | ||||
|         if length == 0 { | ||||
|             self.backward.remove(&count); | ||||
|             self.backward = self | ||||
|                 .backward | ||||
|                 .clone() | ||||
|                 .into_iter() | ||||
|                 .map(|(mut k, v)| { | ||||
|                     k.0 -= count.0; | ||||
|                     (k, v) | ||||
|                 }) | ||||
|                 .collect(); | ||||
|         } | ||||
| 
 | ||||
|         self.size -= 1; | ||||
| 
 | ||||
|         Some(item) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										115
									
								
								src/db_actor.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/db_actor.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| use crate::label::ArbiterLabel; | ||||
| use actix::prelude::*; | ||||
| use bb8_postgres::{bb8, tokio_postgres, PostgresConnectionManager}; | ||||
| use log::{error, info}; | ||||
| use tokio::sync::oneshot::{channel, Receiver}; | ||||
| 
 | ||||
| pub type Pool = bb8::Pool<PostgresConnectionManager<tokio_postgres::tls::NoTls>>; | ||||
| 
 | ||||
| pub enum DbActorState { | ||||
|     Waiting(tokio_postgres::Config), | ||||
|     Ready(Pool), | ||||
| } | ||||
| 
 | ||||
| pub struct DbActor { | ||||
|     pool: DbActorState, | ||||
| } | ||||
| 
 | ||||
| pub struct DbQuery<F>(pub F); | ||||
| 
 | ||||
| impl DbActor { | ||||
|     pub fn new(config: tokio_postgres::Config) -> Addr<Self> { | ||||
|         Supervisor::start(|_| DbActor { | ||||
|             pool: DbActorState::new_empty(config), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl DbActorState { | ||||
|     pub fn new_empty(config: tokio_postgres::Config) -> Self { | ||||
|         DbActorState::Waiting(config) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn new(config: tokio_postgres::Config) -> Result<Self, tokio_postgres::error::Error> { | ||||
|         let manager = PostgresConnectionManager::new(config, tokio_postgres::tls::NoTls); | ||||
|         let pool = bb8::Pool::builder().max_size(8).build(manager).await?; | ||||
| 
 | ||||
|         Ok(DbActorState::Ready(pool)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Actor for DbActor { | ||||
|     type Context = Context<Self>; | ||||
| 
 | ||||
|     fn started(&mut self, ctx: &mut Self::Context) { | ||||
|         info!("Starting DB Actor in {}", ArbiterLabel::get()); | ||||
|         match self.pool { | ||||
|             DbActorState::Waiting(ref config) => { | ||||
|                 let fut = | ||||
|                     DbActorState::new(config.clone()) | ||||
|                         .into_actor(self) | ||||
|                         .map(|res, actor, ctx| { | ||||
|                             match res { | ||||
|                                 Ok(pool) => { | ||||
|                                     info!("DB pool created in {}", ArbiterLabel::get()); | ||||
|                                     actor.pool = pool; | ||||
|                                 } | ||||
|                                 Err(e) => { | ||||
|                                     error!( | ||||
|                                         "Error starting DB Actor in {}, {}", | ||||
|                                         ArbiterLabel::get(), | ||||
|                                         e | ||||
|                                     ); | ||||
|                                     ctx.stop(); | ||||
|                                 } | ||||
|                             }; | ||||
|                         }); | ||||
| 
 | ||||
|                 ctx.wait(fut); | ||||
|             } | ||||
|             _ => (), | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Supervised for DbActor {} | ||||
| 
 | ||||
| impl<F, Fut, R> Handler<DbQuery<F>> for DbActor | ||||
| where | ||||
|     F: Fn(Pool) -> Fut + 'static, | ||||
|     Fut: Future<Output = R>, | ||||
|     R: Send + 'static, | ||||
| { | ||||
|     type Result = ResponseFuture<Receiver<R>>; | ||||
| 
 | ||||
|     fn handle(&mut self, msg: DbQuery<F>, ctx: &mut Self::Context) -> Self::Result { | ||||
|         let (tx, rx) = channel(); | ||||
| 
 | ||||
|         let pool = match self.pool { | ||||
|             DbActorState::Ready(ref pool) => pool.clone(), | ||||
|             _ => { | ||||
|                 error!("Tried to query DB before ready"); | ||||
|                 return Box::pin(async move { rx }); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         ctx.spawn( | ||||
|             async move { | ||||
|                 let result = (msg.0)(pool).await; | ||||
|                 let _ = tx.send(result); | ||||
|             } | ||||
|             .into_actor(self), | ||||
|         ); | ||||
| 
 | ||||
|         Box::pin(async move { rx }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<F, Fut, R> Message for DbQuery<F> | ||||
| where | ||||
|     F: Fn(Pool) -> Fut, | ||||
|     Fut: Future<Output = R>, | ||||
|     R: Send + 'static, | ||||
| { | ||||
|     type Result = Receiver<R>; | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/inbox.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/inbox.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| use activitystreams::primitives::XsdAnyUri; | ||||
| use actix::Addr; | ||||
| use actix_web::{client::Client, web, Responder}; | ||||
| use log::info; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use crate::{ | ||||
|     apub::{AcceptedActors, AcceptedObjects, ValidTypes}, | ||||
|     db_actor::DbActor, | ||||
|     state::State, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Clone, Debug, thiserror::Error)] | ||||
| #[error("Something went wrong :(")] | ||||
| pub struct MyError; | ||||
| 
 | ||||
| pub async fn inbox( | ||||
|     db_actor: web::Data<Addr<DbActor>>, | ||||
|     state: web::Data<State>, | ||||
|     client: web::Data<Client>, | ||||
|     input: web::Json<AcceptedObjects>, | ||||
| ) -> Result<impl Responder, MyError> { | ||||
|     let _state = state.into_inner(); | ||||
|     let input = input.into_inner(); | ||||
| 
 | ||||
|     info!("Relaying {} for {}", input.object.id(), input.actor); | ||||
|     let actor = fetch_actor(client.into_inner(), &input.actor).await?; | ||||
|     info!("Actor, {:#?}", actor); | ||||
| 
 | ||||
|     match input.kind { | ||||
|         ValidTypes::Announce => (), | ||||
|         ValidTypes::Create => (), | ||||
|         ValidTypes::Delete => (), | ||||
|         ValidTypes::Follow => (), | ||||
|         ValidTypes::Undo => (), | ||||
|     } | ||||
| 
 | ||||
|     Ok("{}") | ||||
| } | ||||
| 
 | ||||
| async fn fetch_actor(client: Arc<Client>, actor_id: &XsdAnyUri) -> Result<AcceptedActors, MyError> { | ||||
|     client | ||||
|         .get(actor_id.as_ref()) | ||||
|         .header("Accept", "application/activity+json") | ||||
|         .send() | ||||
|         .await | ||||
|         .map_err(|_| MyError)? | ||||
|         .json() | ||||
|         .await | ||||
|         .map_err(|_| MyError) | ||||
| } | ||||
| 
 | ||||
| impl actix_web::error::ResponseError for MyError {} | ||||
							
								
								
									
										33
									
								
								src/label.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/label.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| use std::sync::{ | ||||
|     atomic::{AtomicUsize, Ordering}, | ||||
|     Arc, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct ArbiterLabelFactory(Arc<AtomicUsize>); | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct ArbiterLabel(usize); | ||||
| 
 | ||||
| impl ArbiterLabelFactory { | ||||
|     pub fn new() -> Self { | ||||
|         ArbiterLabelFactory(Arc::new(AtomicUsize::new(0))) | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_label(&self) { | ||||
|         let id = self.0.fetch_add(1, Ordering::SeqCst); | ||||
|         actix::Arbiter::set_item(ArbiterLabel(id)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ArbiterLabel { | ||||
|     pub fn get() -> ArbiterLabel { | ||||
|         actix::Arbiter::get_item(|label: &ArbiterLabel| label.clone()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for ArbiterLabel { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||||
|         write!(f, "Arbiter #{}", self.0) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| #![feature(drain_filter)] | ||||
| use actix_web::{client::Client, web, App, HttpServer, Responder}; | ||||
| use bb8_postgres::tokio_postgres; | ||||
| 
 | ||||
| mod apub; | ||||
| mod cache; | ||||
| mod db_actor; | ||||
| mod inbox; | ||||
| mod label; | ||||
| mod state; | ||||
| 
 | ||||
| use self::{db_actor::DbActor, label::ArbiterLabelFactory, state::State}; | ||||
| 
 | ||||
| async fn index() -> impl Responder { | ||||
|     "hewwo, mr obama" | ||||
| } | ||||
| 
 | ||||
| #[actix_rt::main] | ||||
| async fn main() -> Result<(), anyhow::Error> { | ||||
|     dotenv::dotenv().ok(); | ||||
|     std::env::set_var("RUST_LOG", "info"); | ||||
|     pretty_env_logger::init(); | ||||
| 
 | ||||
|     let pg_config: tokio_postgres::Config = std::env::var("DATABASE_URL")?.parse()?; | ||||
|     let arbiter_labeler = ArbiterLabelFactory::new(); | ||||
| 
 | ||||
|     let db_actor = DbActor::new(pg_config.clone()); | ||||
|     arbiter_labeler.clone().set_label(); | ||||
| 
 | ||||
|     let state: State = db_actor | ||||
|         .send(db_actor::DbQuery(State::hydrate)) | ||||
|         .await? | ||||
|         .await??; | ||||
| 
 | ||||
|     HttpServer::new(move || { | ||||
|         let actor = DbActor::new(pg_config.clone()); | ||||
|         arbiter_labeler.clone().set_label(); | ||||
|         let client = Client::default(); | ||||
| 
 | ||||
|         App::new() | ||||
|             .data(actor) | ||||
|             .data(state.clone()) | ||||
|             .data(client) | ||||
|             .service(web::resource("/").route(web::get().to(index))) | ||||
|             .service(web::resource("/inbox").route(web::post().to(inbox::inbox))) | ||||
|     }) | ||||
|     .bind("127.0.0.1:8080")? | ||||
|     .run() | ||||
|     .await?; | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/schema.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/schema.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| table! { | ||||
|     blocks (id) { | ||||
|         id -> Uuid, | ||||
|         actor_id -> Text, | ||||
|         created_at -> Timestamp, | ||||
|         updated_at -> Timestamp, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| table! { | ||||
|     listeners (id) { | ||||
|         id -> Uuid, | ||||
|         actor_id -> Text, | ||||
|         created_at -> Timestamp, | ||||
|         updated_at -> Timestamp, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| table! { | ||||
|     whitelists (id) { | ||||
|         id -> Uuid, | ||||
|         actor_id -> Text, | ||||
|         created_at -> Timestamp, | ||||
|         updated_at -> Timestamp, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| allow_tables_to_appear_in_same_query!( | ||||
|     blocks, | ||||
|     listeners, | ||||
|     whitelists, | ||||
| ); | ||||
							
								
								
									
										142
									
								
								src/state.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/state.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | |||
| use activitystreams::primitives::XsdAnyUri; | ||||
| use anyhow::Error; | ||||
| use bb8_postgres::tokio_postgres::{row::Row, Client}; | ||||
| use futures::try_join; | ||||
| use std::{collections::HashSet, sync::Arc}; | ||||
| use tokio::sync::{Mutex, RwLock}; | ||||
| 
 | ||||
| use crate::{cache::WeightedCache, db_actor::Pool}; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct State { | ||||
|     cache: Arc<Mutex<WeightedCache<XsdAnyUri, XsdAnyUri>>>, | ||||
|     blocks: Arc<RwLock<HashSet<XsdAnyUri>>>, | ||||
|     whitelists: Arc<RwLock<HashSet<XsdAnyUri>>>, | ||||
|     listeners: Arc<RwLock<HashSet<XsdAnyUri>>>, | ||||
| } | ||||
| 
 | ||||
| impl State { | ||||
|     pub async fn is_cached(&self, object_id: XsdAnyUri) -> bool { | ||||
|         let cache = self.cache.clone(); | ||||
| 
 | ||||
|         let mut lock = cache.lock().await; | ||||
|         lock.get(object_id).is_some() | ||||
|     } | ||||
| 
 | ||||
|     pub async fn cache(&self, object_id: XsdAnyUri, actor_id: XsdAnyUri) { | ||||
|         let cache = self.cache.clone(); | ||||
| 
 | ||||
|         let mut lock = cache.lock().await; | ||||
|         lock.insert(object_id, actor_id); | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_block(&self, client: &Client, block: XsdAnyUri) -> Result<(), Error> { | ||||
|         let blocks = self.blocks.clone(); | ||||
| 
 | ||||
|         client | ||||
|             .execute( | ||||
|                 "INSERT INTO blocks (actor_id, created_at) VALUES ($1::TEXT, now);", | ||||
|                 &[&block.as_ref()], | ||||
|             ) | ||||
|             .await?; | ||||
| 
 | ||||
|         let mut write_guard = blocks.write().await; | ||||
|         write_guard.insert(block); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_whitelist(&self, client: &Client, whitelist: XsdAnyUri) -> Result<(), Error> { | ||||
|         let whitelists = self.whitelists.clone(); | ||||
| 
 | ||||
|         client | ||||
|             .execute( | ||||
|                 "INSERT INTO whitelists (actor_id, created_at) VALUES ($1::TEXT, now);", | ||||
|                 &[&whitelist.as_ref()], | ||||
|             ) | ||||
|             .await?; | ||||
| 
 | ||||
|         let mut write_guard = whitelists.write().await; | ||||
|         write_guard.insert(whitelist); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn add_listener(&self, client: &Client, listener: XsdAnyUri) -> Result<(), Error> { | ||||
|         let listeners = self.listeners.clone(); | ||||
| 
 | ||||
|         client | ||||
|             .execute( | ||||
|                 "INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, now);", | ||||
|                 &[&listener.as_ref()], | ||||
|             ) | ||||
|             .await?; | ||||
| 
 | ||||
|         let mut write_guard = listeners.write().await; | ||||
|         write_guard.insert(listener); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn hydrate(pool: Pool) -> Result<Self, Error> { | ||||
|         let pool1 = pool.clone(); | ||||
|         let pool2 = pool.clone(); | ||||
| 
 | ||||
|         let f1 = async move { | ||||
|             let conn = pool.get().await?; | ||||
| 
 | ||||
|             hydrate_blocks(&conn).await | ||||
|         }; | ||||
| 
 | ||||
|         let f2 = async move { | ||||
|             let conn = pool1.get().await?; | ||||
| 
 | ||||
|             hydrate_whitelists(&conn).await | ||||
|         }; | ||||
| 
 | ||||
|         let f3 = async move { | ||||
|             let conn = pool2.get().await?; | ||||
| 
 | ||||
|             hydrate_listeners(&conn).await | ||||
|         }; | ||||
| 
 | ||||
|         let (blocks, whitelists, listeners) = try_join!(f1, f2, f3)?; | ||||
| 
 | ||||
|         Ok(State { | ||||
|             cache: Arc::new(Mutex::new(WeightedCache::new(1024 * 8))), | ||||
|             blocks: Arc::new(RwLock::new(blocks)), | ||||
|             whitelists: Arc::new(RwLock::new(whitelists)), | ||||
|             listeners: Arc::new(RwLock::new(listeners)), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub async fn hydrate_blocks(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> { | ||||
|     let rows = client.query("SELECT actor_id FROM blocks", &[]).await?; | ||||
| 
 | ||||
|     parse_rows(rows) | ||||
| } | ||||
| 
 | ||||
| pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> { | ||||
|     let rows = client.query("SELECT actor_id FROM whitelists", &[]).await?; | ||||
| 
 | ||||
|     parse_rows(rows) | ||||
| } | ||||
| 
 | ||||
| pub async fn hydrate_listeners(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> { | ||||
|     let rows = client.query("SELECT actor_id FROM listeners", &[]).await?; | ||||
| 
 | ||||
|     parse_rows(rows) | ||||
| } | ||||
| 
 | ||||
| pub fn parse_rows(rows: Vec<Row>) -> Result<HashSet<XsdAnyUri>, Error> { | ||||
|     let hs = rows | ||||
|         .into_iter() | ||||
|         .filter_map(move |row| { | ||||
|             let s: String = row.try_get("actor_id").ok()?; | ||||
|             s.parse().ok() | ||||
|         }) | ||||
|         .collect(); | ||||
| 
 | ||||
|     Ok(hs) | ||||
| } | ||||
		Loading…
	
	Add table
		
		Reference in a new issue