mirror of
https://git.asonix.dog/asonix/relay.git
synced 2024-11-22 01:21:06 +00:00
bro we are sledding
This commit is contained in:
parent
d7e68190c4
commit
50d2b5b21c
60 changed files with 1261 additions and 2460 deletions
1
.env
1
.env
|
@ -1,2 +1 @@
|
|||
OUT_DIR="compiled_templates"
|
||||
DATABASE_URL=postgres://ap_actix:ap_actix@localhost:5432/ap_actix
|
||||
|
|
562
Cargo.lock
generated
562
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "relay"
|
||||
description = "A simple activitypub relay"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
|
@ -15,9 +15,9 @@ build = "src/build.rs"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
actix-rt = "1.1.1"
|
||||
actix-web = { version = "3.0.1", default-features = false, features = ["rustls", "compress"] }
|
||||
actix-web = { version = "3.3.2", default-features = false, features = ["rustls", "compress"] }
|
||||
actix-webfinger = "0.3.0"
|
||||
activitystreams = "0.7.0-alpha.4"
|
||||
activitystreams = "0.7.0-alpha.9"
|
||||
activitystreams-ext = "0.1.0-alpha.2"
|
||||
ammonia = "3.1.0"
|
||||
async-mutex = "1.0.1"
|
||||
|
@ -27,8 +27,6 @@ background-jobs = "0.8.0"
|
|||
base64 = "0.13"
|
||||
chrono = "0.4.19"
|
||||
config = "0.10.1"
|
||||
deadpool = "0.5.1"
|
||||
deadpool-postgres = "0.5.5"
|
||||
dotenv = "0.15.0"
|
||||
env_logger = "0.8.2"
|
||||
futures = "0.3.4"
|
||||
|
@ -45,9 +43,9 @@ rsa-pem = "0.2.0"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.9"
|
||||
sled = "0.34.6"
|
||||
structopt = "0.3.12"
|
||||
thiserror = "1.0"
|
||||
tokio-postgres = { version = "0.5.1", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
|
||||
ttl_cache = "0.5.1"
|
||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
TAG=$1
|
||||
MIGRATIONS=$2
|
||||
|
||||
function require() {
|
||||
if [ "$1" = "" ]; then
|
||||
|
@ -15,11 +14,10 @@ function print_help() {
|
|||
echo "build.sh"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " build.sh [tag] [migrations]"
|
||||
echo " build.sh [tag]"
|
||||
echo ""
|
||||
echo "Args:"
|
||||
echo " tag: The git tag to create and publish"
|
||||
echo " migrations: (optional) Whether to build the migrations container as well"
|
||||
}
|
||||
|
||||
function build_image() {
|
||||
|
@ -61,12 +59,3 @@ build_image "asonix/relay" "$TAG" "amd64"
|
|||
|
||||
./manifest.sh "asonix/relay" "$TAG"
|
||||
./manifest.sh "asonix/relay" "latest"
|
||||
|
||||
if [ "${MIGRATIONS}" = "migrations" ]; then
|
||||
build_image "asonix/relay-migrations" "$TAG" arm64v8
|
||||
build_image "asonix/relay-migrations" "$TAG" arm32v7
|
||||
build_image "asonix/relay-migrations" "$TAG" amd64
|
||||
|
||||
./manifest.sh "asonix/relay-migrations" "$TAG"
|
||||
./manifest.sh "asonix/relay-migrations" "latest"
|
||||
fi
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
-- 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();
|
|
@ -1,36 +0,0 @@
|
|||
-- 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;
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX listeners_actor_id_index;
|
||||
DROP TABLE listeners;
|
|
@ -1,11 +0,0 @@
|
|||
-- 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
|
||||
);
|
||||
|
||||
CREATE INDEX listeners_actor_id_index ON listeners(actor_id);
|
||||
|
||||
SELECT diesel_manage_updated_at('listeners');
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX blocks_domain_name_index;
|
||||
DROP TABLE blocks;
|
|
@ -1,11 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE blocks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
domain_name TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX blocks_domain_name_index ON blocks(domain_name);
|
||||
|
||||
SELECT diesel_manage_updated_at('blocks');
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX whitelists_domain_name_index;
|
||||
DROP TABLE whitelists;
|
|
@ -1,11 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE whitelists (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
domain_name TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX whitelists_domain_name_index ON whitelists(domain_name);
|
||||
|
||||
SELECT diesel_manage_updated_at('whitelists');
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX settings_key_index;
|
||||
DROP TABLE settings;
|
|
@ -1,12 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE settings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
key TEXT UNIQUE NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX settings_key_index ON settings(key);
|
||||
|
||||
SELECT diesel_manage_updated_at('settings');
|
|
@ -1,8 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TRIGGER IF EXISTS whitelists_notify ON whitelists;
|
||||
DROP TRIGGER IF EXISTS blocks_notify ON blocks;
|
||||
DROP TRIGGER IF EXISTS listeners_notify ON listeners;
|
||||
|
||||
DROP FUNCTION IF EXISTS invoke_whitelists_trigger();
|
||||
DROP FUNCTION IF EXISTS invoke_blocks_trigger();
|
||||
DROP FUNCTION IF EXISTS invoke_listeners_trigger();
|
|
@ -1,99 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE OR REPLACE FUNCTION invoke_listeners_trigger ()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
channel TEXT;
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
case TG_OP
|
||||
WHEN 'INSERT' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_listeners';
|
||||
payload := NEW.actor_id;
|
||||
WHEN 'DELETE' THEN
|
||||
rec := OLD;
|
||||
channel := 'rm_listeners';
|
||||
payload := OLD.actor_id;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||
END CASE;
|
||||
|
||||
PERFORM pg_notify(channel, payload::TEXT);
|
||||
RETURN rec;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION invoke_blocks_trigger ()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
channel TEXT;
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
case TG_OP
|
||||
WHEN 'INSERT' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_blocks';
|
||||
payload := NEW.domain_name;
|
||||
WHEN 'DELETE' THEN
|
||||
rec := OLD;
|
||||
channel := 'rm_blocks';
|
||||
payload := OLD.domain_name;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||
END CASE;
|
||||
|
||||
PERFORM pg_notify(channel, payload::TEXT);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION invoke_whitelists_trigger ()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
channel TEXT;
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
case TG_OP
|
||||
WHEN 'INSERT' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_whitelists';
|
||||
payload := NEW.domain_name;
|
||||
WHEN 'DELETE' THEN
|
||||
rec := OLD;
|
||||
channel := 'rm_whitelists';
|
||||
payload := OLD.domain_name;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||
END CASE;
|
||||
|
||||
PERFORM pg_notify(channel, payload::TEXT);
|
||||
RETURN rec;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER listeners_notify
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON listeners
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE invoke_listeners_trigger();
|
||||
|
||||
CREATE TRIGGER blocks_notify
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON blocks
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE invoke_blocks_trigger();
|
||||
|
||||
CREATE TRIGGER whitelists_notify
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON whitelists
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE invoke_whitelists_trigger();
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX jobs_queue_status_index;
|
||||
DROP TABLE jobs;
|
|
@ -1,17 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
job_id UUID UNIQUE NOT NULL,
|
||||
job_queue TEXT NOT NULL,
|
||||
job_timeout BIGINT NOT NULL,
|
||||
job_updated TIMESTAMP NOT NULL,
|
||||
job_status TEXT NOT NULL,
|
||||
job_value JSONB NOT NULL,
|
||||
job_next_run TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX jobs_queue_status_index ON jobs(job_queue, job_status);
|
||||
|
||||
SELECT diesel_manage_updated_at('jobs');
|
|
@ -1,4 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TRIGGER IF EXISTS actors_notify ON actors;
|
||||
DROP FUNCTION IF EXISTS invoke_actors_trigger();
|
||||
DROP TABLE actors;
|
|
@ -1,49 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE actors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
actor_id TEXT UNIQUE NOT NULL,
|
||||
public_key TEXT NOT NULL,
|
||||
public_key_id TEXT UNIQUE NOT NULL,
|
||||
listener_id UUID NOT NULL REFERENCES listeners(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
SELECT diesel_manage_updated_at('actors');
|
||||
|
||||
CREATE OR REPLACE FUNCTION invoke_actors_trigger ()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
channel TEXT;
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
case TG_OP
|
||||
WHEN 'INSERT' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_actors';
|
||||
payload := NEW.actor_id;
|
||||
WHEN 'UPDATE' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_actors';
|
||||
payload := NEW.actor_id;
|
||||
WHEN 'DELETE' THEN
|
||||
rec := OLD;
|
||||
channel := 'rm_actors';
|
||||
payload := OLD.actor_id;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||
END CASE;
|
||||
|
||||
PERFORM pg_notify(channel, payload::TEXT);
|
||||
RETURN rec;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER actors_notify
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON actors
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE invoke_actors_trigger();
|
|
@ -1,2 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE nodes;
|
|
@ -1,12 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE nodes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
listener_id UUID NOT NULL REFERENCES listeners(id) ON DELETE CASCADE,
|
||||
nodeinfo JSONB,
|
||||
instance JSONB,
|
||||
contact JSONB,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
SELECT diesel_manage_updated_at('nodes');
|
|
@ -1,3 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TRIGGER IF EXISTS nodes_notify ON nodes;
|
||||
DROP FUNCTION IF EXISTS invoke_nodes_trigger();
|
|
@ -1,37 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE OR REPLACE FUNCTION invoke_nodes_trigger ()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
channel TEXT;
|
||||
payload TEXT;
|
||||
BEGIN
|
||||
case TG_OP
|
||||
WHEN 'INSERT' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_nodes';
|
||||
payload := NEW.listener_id;
|
||||
WHEN 'UPDATE' THEN
|
||||
rec := NEW;
|
||||
channel := 'new_nodes';
|
||||
payload := NEW.listener_id;
|
||||
WHEN 'DELETE' THEN
|
||||
rec := OLD;
|
||||
channel := 'rm_nodes';
|
||||
payload := OLD.listener_id;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||
END CASE;
|
||||
|
||||
PERFORM pg_notify(channel, payload::TEXT);
|
||||
RETURN rec;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER nodes_notify
|
||||
AFTER INSERT OR UPDATE OR DELETE
|
||||
ON nodes
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE invoke_nodes_trigger();
|
|
@ -1,2 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE nodes DROP CONSTRAINT nodes_listener_ids_unique;
|
|
@ -1,2 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
ALTER TABLE nodes ADD CONSTRAINT nodes_listener_ids_unique UNIQUE (listener_id);
|
|
@ -1,2 +0,0 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE media;
|
|
@ -1,10 +0,0 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE media (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
media_id UUID UNIQUE NOT NULL,
|
||||
url TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
SELECT diesel_manage_updated_at('media');
|
32
src/args.rs
32
src/args.rs
|
@ -6,25 +6,11 @@ pub struct Args {
|
|||
#[structopt(short, help = "A list of domains that should be blocked")]
|
||||
blocks: Vec<String>,
|
||||
|
||||
#[structopt(short, help = "A list of domains that should be whitelisted")]
|
||||
whitelists: Vec<String>,
|
||||
#[structopt(short, help = "A list of domains that should be allowed")]
|
||||
allowed: Vec<String>,
|
||||
|
||||
#[structopt(short, long, help = "Undo whitelisting or blocking domains")]
|
||||
#[structopt(short, long, help = "Undo allowing or blocking domains")]
|
||||
undo: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long,
|
||||
help = "Only process background jobs, do not start the relay server"
|
||||
)]
|
||||
jobs_only: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long,
|
||||
help = "Only run the relay server, do not process background jobs"
|
||||
)]
|
||||
no_jobs: bool,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
@ -36,19 +22,11 @@ impl Args {
|
|||
&self.blocks
|
||||
}
|
||||
|
||||
pub fn whitelists(&self) -> &[String] {
|
||||
&self.whitelists
|
||||
pub fn allowed(&self) -> &[String] {
|
||||
&self.allowed
|
||||
}
|
||||
|
||||
pub fn undo(&self) -> bool {
|
||||
self.undo
|
||||
}
|
||||
|
||||
pub fn jobs_only(&self) -> bool {
|
||||
self.jobs_only
|
||||
}
|
||||
|
||||
pub fn no_jobs(&self) -> bool {
|
||||
self.no_jobs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use activitystreams::{uri, url::Url};
|
|||
use config::Environment;
|
||||
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::net::IpAddr;
|
||||
use std::{net::IpAddr, path::PathBuf};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
|
@ -17,13 +17,14 @@ pub struct ParsedConfig {
|
|||
addr: IpAddr,
|
||||
port: u16,
|
||||
debug: bool,
|
||||
whitelist_mode: bool,
|
||||
restricted_mode: bool,
|
||||
validate_signatures: bool,
|
||||
https: bool,
|
||||
database_url: String,
|
||||
pretty_log: bool,
|
||||
publish_blocks: bool,
|
||||
max_connections: usize,
|
||||
sled_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -32,13 +33,14 @@ pub struct Config {
|
|||
addr: IpAddr,
|
||||
port: u16,
|
||||
debug: bool,
|
||||
whitelist_mode: bool,
|
||||
restricted_mode: bool,
|
||||
validate_signatures: bool,
|
||||
database_url: String,
|
||||
pretty_log: bool,
|
||||
publish_blocks: bool,
|
||||
max_connections: usize,
|
||||
base_uri: Url,
|
||||
sled_path: PathBuf,
|
||||
}
|
||||
|
||||
pub enum UrlKind {
|
||||
|
@ -62,12 +64,13 @@ impl Config {
|
|||
.set_default("addr", "127.0.0.1")?
|
||||
.set_default("port", 8080)?
|
||||
.set_default("debug", true)?
|
||||
.set_default("whitelist_mode", false)?
|
||||
.set_default("restricted_mode", false)?
|
||||
.set_default("validate_signatures", false)?
|
||||
.set_default("https", false)?
|
||||
.set_default("pretty_log", true)?
|
||||
.set_default("publish_blocks", false)?
|
||||
.set_default("max_connections", 2)?
|
||||
.set_default("sled_path", "./sled/db-0-34")?
|
||||
.merge(Environment::new())?;
|
||||
|
||||
let config: ParsedConfig = config.try_into()?;
|
||||
|
@ -80,16 +83,21 @@ impl Config {
|
|||
addr: config.addr,
|
||||
port: config.port,
|
||||
debug: config.debug,
|
||||
whitelist_mode: config.whitelist_mode,
|
||||
restricted_mode: config.restricted_mode,
|
||||
validate_signatures: config.validate_signatures,
|
||||
database_url: config.database_url,
|
||||
pretty_log: config.pretty_log,
|
||||
publish_blocks: config.publish_blocks,
|
||||
max_connections: config.max_connections,
|
||||
base_uri,
|
||||
sled_path: config.sled_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sled_path(&self) -> &PathBuf {
|
||||
&self.sled_path
|
||||
}
|
||||
|
||||
pub fn pretty_log(&self) -> bool {
|
||||
self.pretty_log
|
||||
}
|
||||
|
@ -135,8 +143,8 @@ impl Config {
|
|||
self.publish_blocks
|
||||
}
|
||||
|
||||
pub fn whitelist_mode(&self) -> bool {
|
||||
self.whitelist_mode
|
||||
pub fn restricted_mode(&self) -> bool {
|
||||
self.restricted_mode
|
||||
}
|
||||
|
||||
pub fn database_url(&self) -> &str {
|
||||
|
@ -156,7 +164,7 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn software_version(&self) -> String {
|
||||
"v0.1.0-main".to_owned()
|
||||
"v0.2.0-main".to_owned()
|
||||
}
|
||||
|
||||
pub fn source_code(&self) -> String {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::{apub::AcceptedActors, db::Db, error::MyError, requests::Requests};
|
||||
use activitystreams::{prelude::*, uri, url::Url};
|
||||
use async_rwlock::RwLock;
|
||||
use log::error;
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use ttl_cache::TtlCache;
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
apub::AcceptedActors,
|
||||
db::{Actor, Db},
|
||||
error::MyError,
|
||||
requests::Requests,
|
||||
};
|
||||
use activitystreams::{prelude::*, url::Url};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const REFETCH_DURATION: Duration = Duration::from_secs(60 * 30);
|
||||
|
||||
|
@ -15,14 +16,14 @@ pub enum MaybeCached<T> {
|
|||
}
|
||||
|
||||
impl<T> MaybeCached<T> {
|
||||
pub fn is_cached(&self) -> bool {
|
||||
pub(crate) fn is_cached(&self) -> bool {
|
||||
match self {
|
||||
MaybeCached::Cached(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
match self {
|
||||
MaybeCached::Cached(t) | MaybeCached::Fetched(t) => t,
|
||||
}
|
||||
|
@ -32,28 +33,43 @@ impl<T> MaybeCached<T> {
|
|||
#[derive(Clone)]
|
||||
pub struct ActorCache {
|
||||
db: Db,
|
||||
cache: Arc<RwLock<TtlCache<Url, Actor>>>,
|
||||
following: Arc<RwLock<HashSet<Url>>>,
|
||||
}
|
||||
|
||||
impl ActorCache {
|
||||
pub fn new(db: Db) -> Self {
|
||||
let cache = ActorCache {
|
||||
db,
|
||||
cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))),
|
||||
following: Arc::new(RwLock::new(HashSet::new())),
|
||||
};
|
||||
|
||||
cache.spawn_rehydrate();
|
||||
|
||||
cache
|
||||
pub(crate) fn new(db: Db) -> Self {
|
||||
ActorCache { db }
|
||||
}
|
||||
|
||||
pub async fn is_following(&self, id: &Url) -> bool {
|
||||
self.following.read().await.contains(id)
|
||||
pub(crate) async fn get(
|
||||
&self,
|
||||
id: &Url,
|
||||
requests: &Requests,
|
||||
) -> Result<MaybeCached<Actor>, MyError> {
|
||||
if let Some(actor) = self.db.actor(id.clone()).await? {
|
||||
if actor.saved_at + REFETCH_DURATION > SystemTime::now() {
|
||||
return Ok(MaybeCached::Cached(actor));
|
||||
}
|
||||
}
|
||||
|
||||
self.get_no_cache(id, requests)
|
||||
.await
|
||||
.map(MaybeCached::Fetched)
|
||||
}
|
||||
|
||||
pub async fn get_no_cache(&self, id: &Url, requests: &Requests) -> Result<Actor, MyError> {
|
||||
pub(crate) async fn follower(&self, actor: Actor) -> Result<(), MyError> {
|
||||
self.db.add_listener(actor.id.clone()).await?;
|
||||
self.db.save_actor(actor).await
|
||||
}
|
||||
|
||||
pub(crate) async fn unfollower(&self, actor: &Actor) -> Result<(), MyError> {
|
||||
self.db.remove_listener(actor.id.clone()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_no_cache(
|
||||
&self,
|
||||
id: &Url,
|
||||
requests: &Requests,
|
||||
) -> Result<Actor, MyError> {
|
||||
let accepted_actor = requests.fetch::<AcceptedActors>(id.as_str()).await?;
|
||||
|
||||
let input_domain = id.domain().ok_or(MyError::MissingDomain)?;
|
||||
|
@ -68,244 +84,13 @@ impl ActorCache {
|
|||
public_key: accepted_actor.ext_one.public_key.public_key_pem,
|
||||
public_key_id: accepted_actor.ext_one.public_key.id,
|
||||
inbox: inbox.into(),
|
||||
saved_at: SystemTime::now(),
|
||||
};
|
||||
|
||||
self.cache
|
||||
.write()
|
||||
.await
|
||||
.insert(id.clone(), actor.clone(), REFETCH_DURATION);
|
||||
|
||||
self.update(id, &actor.public_key, &actor.public_key_id)
|
||||
.await?;
|
||||
self.db.save_actor(actor.clone()).await?;
|
||||
|
||||
Ok(actor)
|
||||
}
|
||||
|
||||
pub async fn get(&self, id: &Url, requests: &Requests) -> Result<MaybeCached<Actor>, MyError> {
|
||||
if let Some(actor) = self.cache.read().await.get(id) {
|
||||
return Ok(MaybeCached::Cached(actor.clone()));
|
||||
}
|
||||
|
||||
if let Some(actor) = self.lookup(id).await? {
|
||||
self.cache
|
||||
.write()
|
||||
.await
|
||||
.insert(id.clone(), actor.clone(), REFETCH_DURATION);
|
||||
return Ok(MaybeCached::Cached(actor));
|
||||
}
|
||||
|
||||
self.get_no_cache(id, requests)
|
||||
.await
|
||||
.map(MaybeCached::Fetched)
|
||||
}
|
||||
|
||||
pub async fn follower(&self, actor: &Actor) -> Result<(), MyError> {
|
||||
self.save(actor.clone()).await
|
||||
}
|
||||
|
||||
pub async fn cache_follower(&self, id: Url) {
|
||||
self.following.write().await.insert(id);
|
||||
}
|
||||
|
||||
pub async fn bust_follower(&self, id: &Url) {
|
||||
self.following.write().await.remove(id);
|
||||
}
|
||||
|
||||
pub async fn unfollower(&self, actor: &Actor) -> Result<Option<Uuid>, MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"DELETE FROM actors
|
||||
WHERE actor_id = $1::TEXT
|
||||
RETURNING listener_id;",
|
||||
&[&actor.id.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let row = if let Some(row) = row_opt {
|
||||
row
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let listener_id: Uuid = row.try_get(0)?;
|
||||
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT FROM actors
|
||||
WHERE listener_id = $1::UUID;",
|
||||
&[&listener_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if row_opt.is_none() {
|
||||
return Ok(Some(listener_id));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn lookup(&self, id: &Url) -> Result<Option<Actor>, MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT listeners.actor_id, actors.public_key, actors.public_key_id
|
||||
FROM listeners
|
||||
INNER JOIN actors ON actors.listener_id = listeners.id
|
||||
WHERE
|
||||
actors.actor_id = $1::TEXT
|
||||
AND
|
||||
actors.updated_at + INTERVAL '120 seconds' < NOW()
|
||||
LIMIT 1;",
|
||||
&[&id.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let row = if let Some(row) = row_opt {
|
||||
row
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let inbox: String = row.try_get(0)?;
|
||||
let public_key_id: String = row.try_get(2)?;
|
||||
|
||||
Ok(Some(Actor {
|
||||
id: id.clone().into(),
|
||||
inbox: uri!(inbox).into(),
|
||||
public_key: row.try_get(1)?,
|
||||
public_key_id: uri!(public_key_id).into(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn save(&self, actor: Actor) -> Result<(), MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;",
|
||||
&[&actor.inbox.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let row = if let Some(row) = row_opt {
|
||||
row
|
||||
} else {
|
||||
return Err(MyError::NotSubscribed(actor.id.as_str().to_owned()));
|
||||
};
|
||||
|
||||
let listener_id: Uuid = row.try_get(0)?;
|
||||
|
||||
self.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"INSERT INTO actors (
|
||||
actor_id,
|
||||
public_key,
|
||||
public_key_id,
|
||||
listener_id,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1::TEXT,
|
||||
$2::TEXT,
|
||||
$3::TEXT,
|
||||
$4::UUID,
|
||||
'now',
|
||||
'now'
|
||||
) ON CONFLICT (actor_id)
|
||||
DO UPDATE SET public_key = $2::TEXT;",
|
||||
&[
|
||||
&actor.id.as_str(),
|
||||
&actor.public_key,
|
||||
&actor.public_key_id.as_str(),
|
||||
&listener_id,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update(&self, id: &Url, public_key: &str, public_key_id: &Url) -> Result<(), MyError> {
|
||||
self.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"UPDATE actors
|
||||
SET public_key = $2::TEXT, public_key_id = $3::TEXT
|
||||
WHERE actor_id = $1::TEXT;",
|
||||
&[&id.as_str(), &public_key, &public_key_id.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_rehydrate(&self) {
|
||||
use actix_rt::time::{interval_at, Instant};
|
||||
|
||||
let this = self.clone();
|
||||
actix_rt::spawn(async move {
|
||||
let mut interval = interval_at(Instant::now(), Duration::from_secs(60 * 10));
|
||||
|
||||
loop {
|
||||
if let Err(e) = this.rehydrate().await {
|
||||
error!("Error rehydrating follows, {}", e);
|
||||
}
|
||||
|
||||
interval.tick().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn rehydrate(&self) -> Result<(), MyError> {
|
||||
let rows = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query("SELECT actor_id FROM actors;", &[])
|
||||
.await?;
|
||||
|
||||
let actor_ids = rows
|
||||
.into_iter()
|
||||
.filter_map(|row| match row.try_get(0) {
|
||||
Ok(s) => {
|
||||
let s: String = s;
|
||||
match s.parse() {
|
||||
Ok(s) => Some(s),
|
||||
Err(e) => {
|
||||
error!("Error parsing actor id, {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting actor id from row, {}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut write_guard = self.following.write().await;
|
||||
*write_guard = actor_ids;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inbox(actor: &AcceptedActors) -> Result<&Url, MyError> {
|
||||
|
@ -314,11 +99,3 @@ fn get_inbox(actor: &AcceptedActors) -> Result<&Url, MyError> {
|
|||
.and_then(|e| e.shared_inbox)
|
||||
.unwrap_or(actor.inbox()?))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Actor {
|
||||
pub id: Url,
|
||||
pub public_key: String,
|
||||
pub public_key_id: Url,
|
||||
pub inbox: Url,
|
||||
}
|
||||
|
|
|
@ -1,171 +1,79 @@
|
|||
use crate::{db::Db, error::MyError};
|
||||
use crate::{
|
||||
db::{Db, MediaMeta},
|
||||
error::MyError,
|
||||
};
|
||||
use activitystreams::url::Url;
|
||||
use actix_web::web::Bytes;
|
||||
use async_mutex::Mutex;
|
||||
use async_rwlock::RwLock;
|
||||
use futures::join;
|
||||
use lru::LruCache;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use ttl_cache::TtlCache;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
static MEDIA_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 2);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Media {
|
||||
pub struct MediaCache {
|
||||
db: Db,
|
||||
inverse: Arc<Mutex<HashMap<Url, Uuid>>>,
|
||||
url_cache: Arc<Mutex<LruCache<Uuid, Url>>>,
|
||||
byte_cache: Arc<RwLock<TtlCache<Uuid, (String, Bytes)>>>,
|
||||
}
|
||||
|
||||
impl Media {
|
||||
impl MediaCache {
|
||||
pub fn new(db: Db) -> Self {
|
||||
Media {
|
||||
db,
|
||||
inverse: Arc::new(Mutex::new(HashMap::new())),
|
||||
url_cache: Arc::new(Mutex::new(LruCache::new(128))),
|
||||
byte_cache: Arc::new(RwLock::new(TtlCache::new(128))),
|
||||
}
|
||||
MediaCache { db }
|
||||
}
|
||||
|
||||
pub async fn get_uuid(&self, url: &Url) -> Result<Option<Uuid>, MyError> {
|
||||
let res = self.inverse.lock().await.get(url).cloned();
|
||||
let uuid = match res {
|
||||
Some(uuid) => uuid,
|
||||
_ => {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT media_id
|
||||
FROM media
|
||||
WHERE url = $1::TEXT
|
||||
LIMIT 1;",
|
||||
&[&url.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row_opt {
|
||||
let uuid: Uuid = row.try_get(0)?;
|
||||
self.inverse.lock().await.insert(url.clone(), uuid);
|
||||
uuid
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.url_cache.lock().await.contains(&uuid) {
|
||||
return Ok(Some(uuid));
|
||||
}
|
||||
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT id
|
||||
FROM media
|
||||
WHERE
|
||||
url = $1::TEXT
|
||||
AND
|
||||
media_id = $2::UUID
|
||||
LIMIT 1;",
|
||||
&[&url.as_str(), &uuid],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if row_opt.is_some() {
|
||||
self.url_cache.lock().await.put(uuid, url.clone());
|
||||
|
||||
return Ok(Some(uuid));
|
||||
}
|
||||
|
||||
self.inverse.lock().await.remove(url);
|
||||
|
||||
Ok(None)
|
||||
pub async fn get_uuid(&self, url: Url) -> Result<Option<Uuid>, MyError> {
|
||||
self.db.media_id(url).await
|
||||
}
|
||||
|
||||
pub async fn get_url(&self, uuid: Uuid) -> Result<Option<Url>, MyError> {
|
||||
if let Some(url) = self.url_cache.lock().await.get(&uuid).cloned() {
|
||||
return Ok(Some(url));
|
||||
self.db.media_url(uuid).await
|
||||
}
|
||||
|
||||
pub async fn is_outdated(&self, uuid: Uuid) -> Result<bool, MyError> {
|
||||
if let Some(meta) = self.db.media_meta(uuid).await? {
|
||||
if meta.saved_at + MEDIA_DURATION > SystemTime::now() {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT url
|
||||
FROM media
|
||||
WHERE media_id = $1::UUID
|
||||
LIMIT 1;",
|
||||
&[&uuid],
|
||||
)
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
if let Some(row) = row_opt {
|
||||
let url: String = row.try_get(0)?;
|
||||
let url: Url = url.parse()?;
|
||||
return Ok(Some(url));
|
||||
pub async fn get_bytes(&self, uuid: Uuid) -> Result<Option<(String, Bytes)>, MyError> {
|
||||
if let Some(meta) = self.db.media_meta(uuid).await? {
|
||||
if meta.saved_at + MEDIA_DURATION > SystemTime::now() {
|
||||
return self
|
||||
.db
|
||||
.media_bytes(uuid)
|
||||
.await
|
||||
.map(|opt| opt.map(|bytes| (meta.media_type, bytes)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn get_bytes(&self, uuid: Uuid) -> Option<(String, Bytes)> {
|
||||
self.byte_cache.read().await.get(&uuid).cloned()
|
||||
}
|
||||
|
||||
pub async fn store_url(&self, url: &Url) -> Result<Uuid, MyError> {
|
||||
pub async fn store_url(&self, url: Url) -> Result<Uuid, MyError> {
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
let (_, _, res) = join!(
|
||||
async {
|
||||
self.inverse.lock().await.insert(url.clone(), uuid);
|
||||
},
|
||||
async {
|
||||
self.url_cache.lock().await.put(uuid, url.clone());
|
||||
},
|
||||
async {
|
||||
self.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"INSERT INTO media (
|
||||
media_id,
|
||||
url,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1::UUID,
|
||||
$2::TEXT,
|
||||
'now',
|
||||
'now'
|
||||
) ON CONFLICT (media_id)
|
||||
DO UPDATE SET url = $2::TEXT;",
|
||||
&[&uuid, &url.as_str()],
|
||||
)
|
||||
.await?;
|
||||
Ok(()) as Result<(), MyError>
|
||||
}
|
||||
);
|
||||
|
||||
res?;
|
||||
self.db.save_url(url, uuid).await?;
|
||||
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
pub async fn store_bytes(&self, uuid: Uuid, content_type: String, bytes: Bytes) {
|
||||
self.byte_cache
|
||||
.write()
|
||||
pub async fn store_bytes(
|
||||
&self,
|
||||
uuid: Uuid,
|
||||
media_type: String,
|
||||
bytes: Bytes,
|
||||
) -> Result<(), MyError> {
|
||||
self.db
|
||||
.save_bytes(
|
||||
uuid,
|
||||
MediaMeta {
|
||||
media_type,
|
||||
saved_at: SystemTime::now(),
|
||||
},
|
||||
bytes,
|
||||
)
|
||||
.await
|
||||
.insert(uuid, (content_type, bytes), MEDIA_DURATION);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ mod media;
|
|||
mod node;
|
||||
mod state;
|
||||
|
||||
pub use self::{
|
||||
actor::{Actor, ActorCache},
|
||||
media::Media,
|
||||
node::{Contact, Info, Instance, Node, NodeCache},
|
||||
state::State,
|
||||
};
|
||||
pub(crate) use actor::ActorCache;
|
||||
pub(crate) use media::MediaCache;
|
||||
pub(crate) use node::{Node, NodeCache};
|
||||
pub(crate) use state::State;
|
||||
|
|
461
src/data/node.rs
461
src/data/node.rs
|
@ -1,341 +1,146 @@
|
|||
use crate::{db::Db, error::MyError};
|
||||
use activitystreams::{uri, url::Url};
|
||||
use async_rwlock::RwLock;
|
||||
use log::{debug, error};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
use crate::{
|
||||
db::{Contact, Db, Info, Instance},
|
||||
error::MyError,
|
||||
};
|
||||
use tokio_postgres::types::Json;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub type ListenersCache = Arc<RwLock<HashSet<Url>>>;
|
||||
use activitystreams::url::Url;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeCache {
|
||||
db: Db,
|
||||
listeners: ListenersCache,
|
||||
nodes: Arc<RwLock<HashMap<Url, Node>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Node {
|
||||
pub(crate) base: Url,
|
||||
pub(crate) info: Option<Info>,
|
||||
pub(crate) instance: Option<Instance>,
|
||||
pub(crate) contact: Option<Contact>,
|
||||
}
|
||||
|
||||
impl NodeCache {
|
||||
pub fn new(db: Db, listeners: ListenersCache) -> Self {
|
||||
NodeCache {
|
||||
db,
|
||||
listeners,
|
||||
nodes: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
pub(crate) fn new(db: Db) -> Self {
|
||||
NodeCache { db }
|
||||
}
|
||||
|
||||
pub async fn nodes(&self) -> Vec<Node> {
|
||||
let listeners: HashSet<_> = self.listeners.read().await.clone();
|
||||
pub(crate) async fn nodes(&self) -> Result<Vec<Node>, MyError> {
|
||||
let infos = self.db.connected_info().await?;
|
||||
let instances = self.db.connected_instance().await?;
|
||||
let contacts = self.db.connected_contact().await?;
|
||||
|
||||
self.nodes
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
if listeners.contains(k) {
|
||||
Some(v.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let vec = self
|
||||
.db
|
||||
.connected_ids()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(move |actor_id| {
|
||||
let info = infos.get(&actor_id).map(|info| info.clone());
|
||||
let instance = instances.get(&actor_id).map(|instance| instance.clone());
|
||||
let contact = contacts.get(&actor_id).map(|contact| contact.clone());
|
||||
|
||||
Node::new(actor_id)
|
||||
.info(info)
|
||||
.instance(instance)
|
||||
.contact(contact)
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
pub async fn is_nodeinfo_outdated(&self, listener: &Url) -> bool {
|
||||
let read_guard = self.nodes.read().await;
|
||||
|
||||
let node = match read_guard.get(listener) {
|
||||
None => {
|
||||
debug!("No node for listener {}", listener);
|
||||
return true;
|
||||
}
|
||||
Some(node) => node,
|
||||
};
|
||||
|
||||
match node.info.as_ref() {
|
||||
Some(nodeinfo) => nodeinfo.outdated(),
|
||||
None => {
|
||||
debug!("No info for node {}", node.base);
|
||||
true
|
||||
}
|
||||
}
|
||||
pub(crate) async fn is_nodeinfo_outdated(&self, actor_id: Url) -> bool {
|
||||
self.db
|
||||
.info(actor_id)
|
||||
.await
|
||||
.map(|opt| opt.map(|info| info.outdated()).unwrap_or(true))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub async fn is_contact_outdated(&self, listener: &Url) -> bool {
|
||||
let read_guard = self.nodes.read().await;
|
||||
|
||||
let node = match read_guard.get(listener) {
|
||||
None => {
|
||||
debug!("No node for listener {}", listener);
|
||||
return true;
|
||||
}
|
||||
Some(node) => node,
|
||||
};
|
||||
|
||||
match node.contact.as_ref() {
|
||||
Some(contact) => contact.outdated(),
|
||||
None => {
|
||||
debug!("No contact for node {}", node.base);
|
||||
true
|
||||
}
|
||||
}
|
||||
pub(crate) async fn is_contact_outdated(&self, actor_id: Url) -> bool {
|
||||
self.db
|
||||
.contact(actor_id)
|
||||
.await
|
||||
.map(|opt| opt.map(|contact| contact.outdated()).unwrap_or(true))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub async fn is_instance_outdated(&self, listener: &Url) -> bool {
|
||||
let read_guard = self.nodes.read().await;
|
||||
|
||||
let node = match read_guard.get(listener) {
|
||||
None => {
|
||||
debug!("No node for listener {}", listener);
|
||||
return true;
|
||||
}
|
||||
Some(node) => node,
|
||||
};
|
||||
|
||||
match node.instance.as_ref() {
|
||||
Some(instance) => instance.outdated(),
|
||||
None => {
|
||||
debug!("No instance for node {}", node.base);
|
||||
true
|
||||
}
|
||||
}
|
||||
pub(crate) async fn is_instance_outdated(&self, actor_id: Url) -> bool {
|
||||
self.db
|
||||
.instance(actor_id)
|
||||
.await
|
||||
.map(|opt| opt.map(|instance| instance.outdated()).unwrap_or(true))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub async fn cache_by_id(&self, id: Uuid) {
|
||||
if let Err(e) = self.do_cache_by_id(id).await {
|
||||
error!("Error loading node into cache, {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bust_by_id(&self, id: Uuid) {
|
||||
if let Err(e) = self.do_bust_by_id(id).await {
|
||||
error!("Error busting node cache, {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_bust_by_id(&self, id: Uuid) -> Result<(), MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT ls.actor_id
|
||||
FROM listeners AS ls
|
||||
INNER JOIN nodes AS nd ON nd.listener_id = ls.id
|
||||
WHERE nd.id = $1::UUID
|
||||
LIMIT 1;",
|
||||
&[&id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let row = if let Some(row) = row_opt {
|
||||
row
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let listener: String = row.try_get(0)?;
|
||||
|
||||
self.nodes.write().await.remove(&uri!(listener));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn do_cache_by_id(&self, id: Uuid) -> Result<(), MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT ls.actor_id, nd.nodeinfo, nd.instance, nd.contact
|
||||
FROM nodes AS nd
|
||||
INNER JOIN listeners AS ls ON nd.listener_id = ls.id
|
||||
WHERE nd.id = $1::UUID
|
||||
LIMIT 1;",
|
||||
&[&id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let row = if let Some(row) = row_opt {
|
||||
row
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let listener: String = row.try_get(0)?;
|
||||
let listener = uri!(listener);
|
||||
let info: Option<Json<Info>> = row.try_get(1)?;
|
||||
let instance: Option<Json<Instance>> = row.try_get(2)?;
|
||||
let contact: Option<Json<Contact>> = row.try_get(3)?;
|
||||
|
||||
{
|
||||
let mut write_guard = self.nodes.write().await;
|
||||
let node = write_guard
|
||||
.entry(listener.clone())
|
||||
.or_insert_with(|| Node::new(listener));
|
||||
|
||||
if let Some(info) = info {
|
||||
node.info = Some(info.0);
|
||||
}
|
||||
if let Some(instance) = instance {
|
||||
node.instance = Some(instance.0);
|
||||
}
|
||||
if let Some(contact) = contact {
|
||||
node.contact = Some(contact.0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_info(
|
||||
pub(crate) async fn set_info(
|
||||
&self,
|
||||
listener: &Url,
|
||||
actor_id: Url,
|
||||
software: String,
|
||||
version: String,
|
||||
reg: bool,
|
||||
) -> Result<(), MyError> {
|
||||
if !self.listeners.read().await.contains(listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(listener);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let node = {
|
||||
let mut write_guard = self.nodes.write().await;
|
||||
let node = write_guard
|
||||
.entry(listener.clone())
|
||||
.or_insert_with(|| Node::new(listener.clone()));
|
||||
node.set_info(software, version, reg);
|
||||
node.clone()
|
||||
};
|
||||
self.save(listener, &node).await?;
|
||||
Ok(())
|
||||
self.db
|
||||
.save_info(
|
||||
actor_id,
|
||||
Info {
|
||||
software,
|
||||
version,
|
||||
reg,
|
||||
updated: SystemTime::now(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_instance(
|
||||
pub(crate) async fn set_instance(
|
||||
&self,
|
||||
listener: &Url,
|
||||
actor_id: Url,
|
||||
title: String,
|
||||
description: String,
|
||||
version: String,
|
||||
reg: bool,
|
||||
requires_approval: bool,
|
||||
) -> Result<(), MyError> {
|
||||
if !self.listeners.read().await.contains(listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(listener);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let node = {
|
||||
let mut write_guard = self.nodes.write().await;
|
||||
let node = write_guard
|
||||
.entry(listener.clone())
|
||||
.or_insert_with(|| Node::new(listener.clone()));
|
||||
node.set_instance(title, description, version, reg, requires_approval);
|
||||
node.clone()
|
||||
};
|
||||
self.save(listener, &node).await?;
|
||||
Ok(())
|
||||
self.db
|
||||
.save_instance(
|
||||
actor_id,
|
||||
Instance {
|
||||
title,
|
||||
description,
|
||||
version,
|
||||
reg,
|
||||
requires_approval,
|
||||
updated: SystemTime::now(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_contact(
|
||||
pub(crate) async fn set_contact(
|
||||
&self,
|
||||
listener: &Url,
|
||||
actor_id: Url,
|
||||
username: String,
|
||||
display_name: String,
|
||||
url: Url,
|
||||
avatar: Url,
|
||||
) -> Result<(), MyError> {
|
||||
if !self.listeners.read().await.contains(listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(listener);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let node = {
|
||||
let mut write_guard = self.nodes.write().await;
|
||||
let node = write_guard
|
||||
.entry(listener.clone())
|
||||
.or_insert_with(|| Node::new(listener.clone()));
|
||||
node.set_contact(username, display_name, url, avatar);
|
||||
node.clone()
|
||||
};
|
||||
self.save(listener, &node).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn save(&self, listener: &Url, node: &Node) -> Result<(), MyError> {
|
||||
let row_opt = self
|
||||
.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.query_opt(
|
||||
"SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;",
|
||||
&[&listener.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let id: Uuid = if let Some(row) = row_opt {
|
||||
row.try_get(0)?
|
||||
} else {
|
||||
return Err(MyError::NotSubscribed(listener.as_str().to_owned()));
|
||||
};
|
||||
|
||||
self.db
|
||||
.pool()
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"INSERT INTO nodes (
|
||||
listener_id,
|
||||
nodeinfo,
|
||||
instance,
|
||||
contact,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
$1::UUID,
|
||||
$2::JSONB,
|
||||
$3::JSONB,
|
||||
$4::JSONB,
|
||||
'now',
|
||||
'now'
|
||||
) ON CONFLICT (listener_id)
|
||||
DO UPDATE SET
|
||||
nodeinfo = $2::JSONB,
|
||||
instance = $3::JSONB,
|
||||
contact = $4::JSONB;",
|
||||
&[
|
||||
&id,
|
||||
&Json(&node.info),
|
||||
&Json(&node.instance),
|
||||
&Json(&node.contact),
|
||||
],
|
||||
.save_contact(
|
||||
actor_id,
|
||||
Contact {
|
||||
username,
|
||||
display_name,
|
||||
url,
|
||||
avatar,
|
||||
updated: SystemTime::now(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Node {
|
||||
pub base: Url,
|
||||
pub info: Option<Info>,
|
||||
pub instance: Option<Instance>,
|
||||
pub contact: Option<Contact>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new(mut url: Url) -> Self {
|
||||
fn new(mut url: Url) -> Self {
|
||||
url.set_fragment(None);
|
||||
url.set_query(None);
|
||||
url.set_path("");
|
||||
|
@ -348,96 +153,38 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_info(&mut self, software: String, version: String, reg: bool) -> &mut Self {
|
||||
self.info = Some(Info {
|
||||
software,
|
||||
version,
|
||||
reg,
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
fn info(mut self, info: Option<Info>) -> Self {
|
||||
self.info = info;
|
||||
self
|
||||
}
|
||||
|
||||
fn set_instance(
|
||||
&mut self,
|
||||
title: String,
|
||||
description: String,
|
||||
version: String,
|
||||
reg: bool,
|
||||
requires_approval: bool,
|
||||
) -> &mut Self {
|
||||
self.instance = Some(Instance {
|
||||
title,
|
||||
description,
|
||||
version,
|
||||
reg,
|
||||
requires_approval,
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
fn instance(mut self, instance: Option<Instance>) -> Self {
|
||||
self.instance = instance;
|
||||
self
|
||||
}
|
||||
|
||||
fn set_contact(
|
||||
&mut self,
|
||||
username: String,
|
||||
display_name: String,
|
||||
url: Url,
|
||||
avatar: Url,
|
||||
) -> &mut Self {
|
||||
self.contact = Some(Contact {
|
||||
username,
|
||||
display_name,
|
||||
url: url.into(),
|
||||
avatar: avatar.into(),
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
fn contact(mut self, contact: Option<Contact>) -> Self {
|
||||
self.contact = contact;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Info {
|
||||
pub software: String,
|
||||
pub version: String,
|
||||
pub reg: bool,
|
||||
pub updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Instance {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub reg: bool,
|
||||
pub requires_approval: bool,
|
||||
pub updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Contact {
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub url: Url,
|
||||
pub avatar: Url,
|
||||
pub updated: SystemTime,
|
||||
}
|
||||
|
||||
static TEN_MINUTES: Duration = Duration::from_secs(60 * 10);
|
||||
|
||||
impl Info {
|
||||
pub fn outdated(&self) -> bool {
|
||||
pub(crate) fn outdated(&self) -> bool {
|
||||
self.updated + TEN_MINUTES < SystemTime::now()
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn outdated(&self) -> bool {
|
||||
pub(crate) fn outdated(&self) -> bool {
|
||||
self.updated + TEN_MINUTES < SystemTime::now()
|
||||
}
|
||||
}
|
||||
|
||||
impl Contact {
|
||||
pub fn outdated(&self) -> bool {
|
||||
pub(crate) fn outdated(&self) -> bool {
|
||||
self.updated + TEN_MINUTES < SystemTime::now()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,38 +6,31 @@ use crate::{
|
|||
requests::{Breakers, Requests},
|
||||
};
|
||||
use activitystreams::url::Url;
|
||||
use actix_rt::{
|
||||
spawn,
|
||||
time::{interval_at, Instant},
|
||||
};
|
||||
use actix_web::web;
|
||||
use async_rwlock::RwLock;
|
||||
use futures::{join, try_join};
|
||||
use log::{error, info};
|
||||
use log::info;
|
||||
use lru::LruCache;
|
||||
use rand::thread_rng;
|
||||
use rsa::{RSAPrivateKey, RSAPublicKey};
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct State {
|
||||
pub public_key: RSAPublicKey,
|
||||
pub(crate) public_key: RSAPublicKey,
|
||||
private_key: RSAPrivateKey,
|
||||
config: Config,
|
||||
actor_id_cache: Arc<RwLock<LruCache<Url, Url>>>,
|
||||
blocks: Arc<RwLock<HashSet<String>>>,
|
||||
whitelists: Arc<RwLock<HashSet<String>>>,
|
||||
listeners: Arc<RwLock<HashSet<Url>>>,
|
||||
object_cache: Arc<RwLock<LruCache<Url, Url>>>,
|
||||
node_cache: NodeCache,
|
||||
breakers: Breakers,
|
||||
pub(crate) db: Db,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn node_cache(&self) -> NodeCache {
|
||||
pub(crate) fn node_cache(&self) -> NodeCache {
|
||||
self.node_cache.clone()
|
||||
}
|
||||
|
||||
pub fn requests(&self) -> Requests {
|
||||
pub(crate) fn requests(&self) -> Requests {
|
||||
Requests::new(
|
||||
self.config.generate_url(UrlKind::MainKey).to_string(),
|
||||
self.private_key.clone(),
|
||||
|
@ -51,168 +44,64 @@ impl State {
|
|||
)
|
||||
}
|
||||
|
||||
pub async fn bust_whitelist(&self, whitelist: &str) {
|
||||
self.whitelists.write().await.remove(whitelist);
|
||||
}
|
||||
|
||||
pub async fn bust_block(&self, block: &str) {
|
||||
self.blocks.write().await.remove(block);
|
||||
}
|
||||
|
||||
pub async fn bust_listener(&self, inbox: &Url) {
|
||||
self.listeners.write().await.remove(inbox);
|
||||
}
|
||||
|
||||
pub async fn listeners(&self) -> Vec<Url> {
|
||||
self.listeners.read().await.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub async fn blocks(&self) -> Vec<String> {
|
||||
self.blocks.read().await.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub async fn listeners_without(&self, inbox: &Url, domain: &str) -> Vec<Url> {
|
||||
self.listeners
|
||||
.read()
|
||||
.await
|
||||
pub(crate) async fn inboxes_without(
|
||||
&self,
|
||||
existing_inbox: &Url,
|
||||
domain: &str,
|
||||
) -> Result<Vec<Url>, MyError> {
|
||||
Ok(self
|
||||
.db
|
||||
.inboxes()
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|listener| {
|
||||
if let Some(dom) = listener.domain() {
|
||||
if listener != inbox && dom != domain {
|
||||
return Some(listener.clone());
|
||||
.filter_map(|inbox| {
|
||||
if let Some(dom) = inbox.domain() {
|
||||
if inbox != existing_inbox && dom != domain {
|
||||
return Some(inbox.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn is_whitelisted(&self, actor_id: &Url) -> bool {
|
||||
if !self.config.whitelist_mode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(domain) = actor_id.domain() {
|
||||
return self.whitelists.read().await.contains(domain);
|
||||
}
|
||||
|
||||
false
|
||||
pub(crate) async fn is_cached(&self, object_id: &Url) -> bool {
|
||||
self.object_cache.read().await.contains(object_id)
|
||||
}
|
||||
|
||||
pub async fn is_blocked(&self, actor_id: &Url) -> bool {
|
||||
if let Some(domain) = actor_id.domain() {
|
||||
return self.blocks.read().await.contains(domain);
|
||||
}
|
||||
|
||||
true
|
||||
pub(crate) async fn cache(&self, object_id: Url, actor_id: Url) {
|
||||
self.object_cache.write().await.put(object_id, actor_id);
|
||||
}
|
||||
|
||||
pub async fn is_listener(&self, actor_id: &Url) -> bool {
|
||||
self.listeners.read().await.contains(actor_id)
|
||||
}
|
||||
pub(crate) async fn build(config: Config, db: Db) -> Result<Self, MyError> {
|
||||
let private_key = if let Ok(Some(key)) = db.private_key().await {
|
||||
key
|
||||
} else {
|
||||
info!("Generating new keys");
|
||||
let key = web::block(move || {
|
||||
let mut rng = thread_rng();
|
||||
RSAPrivateKey::new(&mut rng, 4096)
|
||||
})
|
||||
.await?;
|
||||
|
||||
pub async fn is_cached(&self, object_id: &Url) -> bool {
|
||||
self.actor_id_cache.read().await.contains(object_id)
|
||||
}
|
||||
db.update_private_key(&key).await?;
|
||||
|
||||
pub async fn cache(&self, object_id: Url, actor_id: Url) {
|
||||
self.actor_id_cache.write().await.put(object_id, actor_id);
|
||||
}
|
||||
|
||||
pub async fn cache_block(&self, host: String) {
|
||||
self.blocks.write().await.insert(host);
|
||||
}
|
||||
|
||||
pub async fn cache_whitelist(&self, host: String) {
|
||||
self.whitelists.write().await.insert(host);
|
||||
}
|
||||
|
||||
pub async fn cache_listener(&self, listener: Url) {
|
||||
self.listeners.write().await.insert(listener);
|
||||
}
|
||||
|
||||
pub async fn rehydrate(&self, db: &Db) -> Result<(), MyError> {
|
||||
let f1 = db.hydrate_blocks();
|
||||
let f2 = db.hydrate_whitelists();
|
||||
let f3 = db.hydrate_listeners();
|
||||
|
||||
let (blocks, whitelists, listeners) = try_join!(f1, f2, f3)?;
|
||||
|
||||
join!(
|
||||
async move {
|
||||
*self.listeners.write().await = listeners;
|
||||
},
|
||||
async move {
|
||||
*self.whitelists.write().await = whitelists;
|
||||
},
|
||||
async move {
|
||||
*self.blocks.write().await = blocks;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn hydrate(config: Config, db: &Db) -> Result<Self, MyError> {
|
||||
let f1 = db.hydrate_blocks();
|
||||
let f2 = db.hydrate_whitelists();
|
||||
let f3 = db.hydrate_listeners();
|
||||
|
||||
let f4 = async move {
|
||||
if let Ok(Some(key)) = db.hydrate_private_key().await {
|
||||
Ok(key)
|
||||
} else {
|
||||
info!("Generating new keys");
|
||||
let key = web::block(move || {
|
||||
let mut rng = thread_rng();
|
||||
RSAPrivateKey::new(&mut rng, 4096)
|
||||
})
|
||||
.await?;
|
||||
|
||||
db.update_private_key(&key).await?;
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
key
|
||||
};
|
||||
|
||||
let (blocks, whitelists, listeners, private_key) = try_join!(f1, f2, f3, f4)?;
|
||||
|
||||
let public_key = private_key.to_public_key();
|
||||
let listeners = Arc::new(RwLock::new(listeners));
|
||||
|
||||
let state = State {
|
||||
public_key,
|
||||
private_key,
|
||||
config,
|
||||
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
||||
blocks: Arc::new(RwLock::new(blocks)),
|
||||
whitelists: Arc::new(RwLock::new(whitelists)),
|
||||
listeners: listeners.clone(),
|
||||
node_cache: NodeCache::new(db.clone(), listeners),
|
||||
object_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
||||
node_cache: NodeCache::new(db.clone()),
|
||||
breakers: Breakers::default(),
|
||||
db,
|
||||
};
|
||||
|
||||
state.spawn_rehydrate(db.clone());
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn spawn_rehydrate(&self, db: Db) {
|
||||
let state = self.clone();
|
||||
spawn(async move {
|
||||
let start = Instant::now();
|
||||
let duration = Duration::from_secs(60 * 10);
|
||||
|
||||
let mut interval = interval_at(start, duration);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
if let Err(e) = state.rehydrate(&db).await {
|
||||
error!("Error rehydrating, {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
863
src/db.rs
863
src/db.rs
|
@ -1,294 +1,619 @@
|
|||
use crate::error::MyError;
|
||||
use crate::{config::Config, error::MyError};
|
||||
use activitystreams::url::Url;
|
||||
use deadpool_postgres::{Manager, Pool};
|
||||
use log::{info, warn};
|
||||
use actix_web::web::Bytes;
|
||||
use rsa::RSAPrivateKey;
|
||||
use rsa_pem::KeyExt;
|
||||
use std::collections::HashSet;
|
||||
use tokio_postgres::{
|
||||
error::{Error, SqlState},
|
||||
row::Row,
|
||||
Client, Config, NoTls,
|
||||
};
|
||||
use sled::Tree;
|
||||
use std::{collections::HashMap, sync::Arc, time::SystemTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Db {
|
||||
pool: Pool,
|
||||
inner: Arc<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
actor_id_actor: Tree,
|
||||
public_key_id_actor_id: Tree,
|
||||
connected_actor_ids: Tree,
|
||||
allowed_domains: Tree,
|
||||
blocked_domains: Tree,
|
||||
settings: Tree,
|
||||
media_url_media_id: Tree,
|
||||
media_id_media_url: Tree,
|
||||
media_id_media_bytes: Tree,
|
||||
media_id_media_meta: Tree,
|
||||
actor_id_info: Tree,
|
||||
actor_id_instance: Tree,
|
||||
actor_id_contact: Tree,
|
||||
restricted_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Actor {
|
||||
pub(crate) id: Url,
|
||||
pub(crate) public_key: String,
|
||||
pub(crate) public_key_id: Url,
|
||||
pub(crate) inbox: Url,
|
||||
pub(crate) saved_at: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct MediaMeta {
|
||||
pub(crate) media_type: String,
|
||||
pub(crate) saved_at: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Info {
|
||||
pub(crate) software: String,
|
||||
pub(crate) version: String,
|
||||
pub(crate) reg: bool,
|
||||
pub(crate) updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Instance {
|
||||
pub(crate) title: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) version: String,
|
||||
pub(crate) reg: bool,
|
||||
pub(crate) requires_approval: bool,
|
||||
pub(crate) updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Contact {
|
||||
pub(crate) username: String,
|
||||
pub(crate) display_name: String,
|
||||
pub(crate) url: Url,
|
||||
pub(crate) avatar: Url,
|
||||
pub(crate) updated: SystemTime,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn connected_by_domain(&self, domains: &[String]) -> impl DoubleEndedIterator<Item = Url> {
|
||||
let reversed: Vec<_> = domains
|
||||
.into_iter()
|
||||
.map(|s| domain_key(s.as_str()))
|
||||
.collect();
|
||||
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(url_from_ivec)
|
||||
.filter_map(move |url| {
|
||||
let connected_domain = url.domain()?;
|
||||
let connected_rdnn = domain_key(connected_domain);
|
||||
|
||||
for rdnn in &reversed {
|
||||
if connected_rdnn.starts_with(rdnn) {
|
||||
return Some(url);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn blocks(&self) -> impl DoubleEndedIterator<Item = String> {
|
||||
self.blocked_domains
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.map(|s| String::from_utf8_lossy(&s).to_string())
|
||||
}
|
||||
|
||||
fn connected(&self) -> impl DoubleEndedIterator<Item = Url> {
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(url_from_ivec)
|
||||
}
|
||||
|
||||
fn connected_actors<'a>(&'a self) -> impl DoubleEndedIterator<Item = Actor> + 'a {
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(move |actor_id| {
|
||||
let actor_ivec = self.actor_id_actor.get(actor_id).ok()??;
|
||||
|
||||
serde_json::from_slice::<Actor>(&actor_ivec).ok()
|
||||
})
|
||||
}
|
||||
|
||||
fn connected_info<'a>(&'a self) -> impl DoubleEndedIterator<Item = (Url, Info)> + 'a {
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(move |actor_id_ivec| {
|
||||
let actor_id = url_from_ivec(actor_id_ivec.clone())?;
|
||||
let ivec = self.actor_id_info.get(actor_id_ivec).ok()??;
|
||||
let info = serde_json::from_slice(&ivec).ok()?;
|
||||
|
||||
Some((actor_id, info))
|
||||
})
|
||||
}
|
||||
|
||||
fn connected_instance<'a>(&'a self) -> impl DoubleEndedIterator<Item = (Url, Instance)> + 'a {
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(move |actor_id_ivec| {
|
||||
let actor_id = url_from_ivec(actor_id_ivec.clone())?;
|
||||
let ivec = self.actor_id_instance.get(actor_id_ivec).ok()??;
|
||||
let instance = serde_json::from_slice(&ivec).ok()?;
|
||||
|
||||
Some((actor_id, instance))
|
||||
})
|
||||
}
|
||||
|
||||
fn connected_contact<'a>(&'a self) -> impl DoubleEndedIterator<Item = (Url, Contact)> + 'a {
|
||||
self.connected_actor_ids
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(move |actor_id_ivec| {
|
||||
let actor_id = url_from_ivec(actor_id_ivec.clone())?;
|
||||
let ivec = self.actor_id_contact.get(actor_id_ivec).ok()??;
|
||||
let contact = serde_json::from_slice(&ivec).ok()?;
|
||||
|
||||
Some((actor_id, contact))
|
||||
})
|
||||
}
|
||||
|
||||
fn is_allowed(&self, domain: &str) -> bool {
|
||||
let prefix = domain_prefix(domain);
|
||||
|
||||
if self.restricted_mode {
|
||||
self.allowed_domains
|
||||
.scan_prefix(prefix)
|
||||
.keys()
|
||||
.filter_map(|res| res.ok())
|
||||
.any(|rdnn| domain.starts_with(String::from_utf8_lossy(&rdnn).as_ref()))
|
||||
} else {
|
||||
!self
|
||||
.blocked_domains
|
||||
.scan_prefix(prefix)
|
||||
.keys()
|
||||
.filter_map(|res| res.ok())
|
||||
.any(|rdnn| domain.starts_with(String::from_utf8_lossy(&rdnn).as_ref()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub fn build(config: &crate::config::Config) -> Result<Self, MyError> {
|
||||
let max_conns = config.max_connections();
|
||||
let config: Config = config.database_url().parse()?;
|
||||
|
||||
let manager = Manager::new(config, NoTls);
|
||||
pub(crate) fn build(config: &Config) -> Result<Self, MyError> {
|
||||
let db = sled::open(config.sled_path())?;
|
||||
let restricted_mode = config.restricted_mode();
|
||||
|
||||
Ok(Db {
|
||||
pool: Pool::new(manager, max_conns),
|
||||
inner: Arc::new(Inner {
|
||||
actor_id_actor: db.open_tree("actor-id-actor")?,
|
||||
public_key_id_actor_id: db.open_tree("public-key-id-actor-id")?,
|
||||
connected_actor_ids: db.open_tree("connected-actor-ids")?,
|
||||
allowed_domains: db.open_tree("allowed-actor-ids")?,
|
||||
blocked_domains: db.open_tree("blocked-actor-ids")?,
|
||||
settings: db.open_tree("settings")?,
|
||||
media_url_media_id: db.open_tree("media-url-media-id")?,
|
||||
media_id_media_url: db.open_tree("media-id-media-url")?,
|
||||
media_id_media_bytes: db.open_tree("media-id-media-bytes")?,
|
||||
media_id_media_meta: db.open_tree("media-id-media-meta")?,
|
||||
actor_id_info: db.open_tree("actor-id-info")?,
|
||||
actor_id_instance: db.open_tree("actor-id-instance")?,
|
||||
actor_id_contact: db.open_tree("actor-id-contact")?,
|
||||
restricted_mode,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pool(&self) -> &Pool {
|
||||
&self.pool
|
||||
async fn unblock<T>(
|
||||
&self,
|
||||
f: impl Fn(&Inner) -> Result<T, MyError> + Send + 'static,
|
||||
) -> Result<T, MyError>
|
||||
where
|
||||
T: Send + 'static,
|
||||
{
|
||||
let inner = self.inner.clone();
|
||||
|
||||
let t = actix_web::web::block(move || (f)(&inner)).await?;
|
||||
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
pub async fn remove_listener(&self, inbox: Url) -> Result<(), MyError> {
|
||||
info!("DELETE FROM listeners WHERE actor_id = {};", inbox.as_str());
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"DELETE FROM listeners WHERE actor_id = $1::TEXT;",
|
||||
&[&inbox.as_str()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
pub(crate) async fn connected_ids(&self) -> Result<Vec<Url>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.connected().collect())).await
|
||||
}
|
||||
|
||||
pub async fn add_listener(&self, inbox: Url) -> Result<(), MyError> {
|
||||
info!(
|
||||
"INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||
inbox.as_str(),
|
||||
);
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, 'now');",
|
||||
&[&inbox.as_str()],
|
||||
)
|
||||
.await?;
|
||||
pub(crate) async fn save_info(&self, actor_id: Url, info: Info) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&info)?;
|
||||
|
||||
Ok(())
|
||||
inner
|
||||
.actor_id_info
|
||||
.insert(actor_id.as_str().as_bytes(), vec)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn add_blocks(&self, domains: &[String]) -> Result<(), MyError> {
|
||||
let conn = self.pool.get().await?;
|
||||
for domain in domains {
|
||||
match add_block(&conn, domain.as_str()).await {
|
||||
Err(e) if e.code() != Some(&SqlState::UNIQUE_VIOLATION) => {
|
||||
return Err(e.into());
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_blocks(&self, domains: &[String]) -> Result<(), MyError> {
|
||||
let conn = self.pool.get().await?;
|
||||
for domain in domains {
|
||||
remove_block(&conn, domain.as_str()).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_whitelists(&self, domains: &[String]) -> Result<(), MyError> {
|
||||
let conn = self.pool.get().await?;
|
||||
for domain in domains {
|
||||
match add_whitelist(&conn, domain.as_str()).await {
|
||||
Err(e) if e.code() != Some(&SqlState::UNIQUE_VIOLATION) => {
|
||||
return Err(e.into());
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_whitelists(&self, domains: &[String]) -> Result<(), MyError> {
|
||||
let conn = self.pool.get().await?;
|
||||
for domain in domains {
|
||||
remove_whitelist(&conn, domain.as_str()).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn hydrate_blocks(&self) -> Result<HashSet<String>, MyError> {
|
||||
info!("SELECT domain_name FROM blocks");
|
||||
let rows = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.query("SELECT domain_name FROM blocks", &[])
|
||||
.await?;
|
||||
|
||||
parse_rows(rows)
|
||||
}
|
||||
|
||||
pub async fn hydrate_whitelists(&self) -> Result<HashSet<String>, MyError> {
|
||||
info!("SELECT domain_name FROM whitelists");
|
||||
let rows = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.query("SELECT domain_name FROM whitelists", &[])
|
||||
.await?;
|
||||
|
||||
parse_rows(rows)
|
||||
}
|
||||
|
||||
pub async fn hydrate_listeners(&self) -> Result<HashSet<Url>, MyError> {
|
||||
info!("SELECT actor_id FROM listeners");
|
||||
let rows = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.query("SELECT actor_id FROM listeners", &[])
|
||||
.await?;
|
||||
|
||||
parse_rows(rows)
|
||||
}
|
||||
|
||||
pub async fn hydrate_private_key(&self) -> Result<Option<RSAPrivateKey>, MyError> {
|
||||
info!("SELECT value FROM settings WHERE key = 'private_key'");
|
||||
let rows = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.query("SELECT value FROM settings WHERE key = 'private_key'", &[])
|
||||
.await?;
|
||||
|
||||
if let Some(row) = rows.into_iter().next() {
|
||||
let key_str: String = row.get(0);
|
||||
// precomputation happens when constructing a private key, so it should be on the
|
||||
// threadpool
|
||||
let key = actix_web::web::block(move || KeyExt::from_pem_pkcs8(&key_str)).await?;
|
||||
|
||||
return Ok(Some(key));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn update_private_key(&self, private_key: &RSAPrivateKey) -> Result<(), MyError> {
|
||||
let pem_pkcs8 = private_key.to_pem_pkcs8()?;
|
||||
|
||||
info!(
|
||||
"INSERT INTO settings (key, value, created_at)
|
||||
VALUES ('private_key', $1::TEXT, 'now')
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE
|
||||
SET value = $1::TEXT;"
|
||||
);
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.execute(
|
||||
"INSERT INTO settings (key, value, created_at)
|
||||
VALUES ('private_key', $1::TEXT, 'now')
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE
|
||||
SET value = $1::TEXT;",
|
||||
&[&pem_pkcs8],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn listen(client: &Client) -> Result<(), Error> {
|
||||
info!("LISTEN new_blocks, new_whitelists, new_listeners, new_actors, rm_blocks, rm_whitelists, rm_listeners, rm_actors");
|
||||
client
|
||||
.batch_execute(
|
||||
"LISTEN new_blocks;
|
||||
LISTEN new_whitelists;
|
||||
LISTEN new_listeners;
|
||||
LISTEN new_actors;
|
||||
LISTEN new_nodes;
|
||||
LISTEN rm_blocks;
|
||||
LISTEN rm_whitelists;
|
||||
LISTEN rm_listeners;
|
||||
LISTEN rm_actors;
|
||||
LISTEN rm_nodes",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_block(client: &Client, domain: &str) -> Result<(), Error> {
|
||||
info!(
|
||||
"INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||
domain,
|
||||
);
|
||||
client
|
||||
.execute(
|
||||
"INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now');",
|
||||
&[&domain],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_block(client: &Client, domain: &str) -> Result<(), Error> {
|
||||
info!(
|
||||
"DELETE FROM blocks WHERE domain_name = $1::TEXT; [{}]",
|
||||
domain,
|
||||
);
|
||||
client
|
||||
.execute(
|
||||
"DELETE FROM blocks WHERE domain_name = $1::TEXT;",
|
||||
&[&domain],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_whitelist(client: &Client, domain: &str) -> Result<(), Error> {
|
||||
info!(
|
||||
"INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||
domain,
|
||||
);
|
||||
client
|
||||
.execute(
|
||||
"INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now');",
|
||||
&[&domain],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_whitelist(client: &Client, domain: &str) -> Result<(), Error> {
|
||||
info!(
|
||||
"DELETE FROM whitelists WHERE domain_name = $1::TEXT; [{}]",
|
||||
domain,
|
||||
);
|
||||
client
|
||||
.execute(
|
||||
"DELETE FROM whitelists WHERE domain_name = $1::TEXT;",
|
||||
&[&domain],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_rows<T, E>(rows: Vec<Row>) -> Result<HashSet<T>, MyError>
|
||||
where
|
||||
T: std::str::FromStr<Err = E> + Eq + std::hash::Hash,
|
||||
E: std::fmt::Display,
|
||||
{
|
||||
let hs = rows
|
||||
.into_iter()
|
||||
.filter_map(move |row| match row.try_get::<_, String>(0) {
|
||||
Ok(s) => match s.parse() {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
warn!("Couln't parse row, '{}', {}", s, e);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Couldn't get column, {}", e);
|
||||
None
|
||||
pub(crate) async fn info(&self, actor_id: Url) -> Result<Option<Info>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.actor_id_info.get(actor_id.as_str().as_bytes())? {
|
||||
let info = serde_json::from_slice(&ivec)?;
|
||||
Ok(Some(info))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(hs)
|
||||
pub(crate) async fn connected_info(&self) -> Result<HashMap<Url, Info>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.connected_info().collect()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_instance(
|
||||
&self,
|
||||
actor_id: Url,
|
||||
instance: Instance,
|
||||
) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&instance)?;
|
||||
|
||||
inner
|
||||
.actor_id_instance
|
||||
.insert(actor_id.as_str().as_bytes(), vec)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn instance(&self, actor_id: Url) -> Result<Option<Instance>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.actor_id_instance.get(actor_id.as_str().as_bytes())? {
|
||||
let instance = serde_json::from_slice(&ivec)?;
|
||||
Ok(Some(instance))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn connected_instance(&self) -> Result<HashMap<Url, Instance>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.connected_instance().collect()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_contact(
|
||||
&self,
|
||||
actor_id: Url,
|
||||
contact: Contact,
|
||||
) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&contact)?;
|
||||
|
||||
inner
|
||||
.actor_id_contact
|
||||
.insert(actor_id.as_str().as_bytes(), vec)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn contact(&self, actor_id: Url) -> Result<Option<Contact>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.actor_id_contact.get(actor_id.as_str().as_bytes())? {
|
||||
let contact = serde_json::from_slice(&ivec)?;
|
||||
Ok(Some(contact))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn connected_contact(&self) -> Result<HashMap<Url, Contact>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.connected_contact().collect()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_url(&self, url: Url, id: Uuid) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
inner
|
||||
.media_id_media_url
|
||||
.insert(id.as_bytes(), url.as_str().as_bytes())?;
|
||||
inner
|
||||
.media_url_media_id
|
||||
.insert(url.as_str().as_bytes(), id.as_bytes())?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_bytes(
|
||||
&self,
|
||||
id: Uuid,
|
||||
meta: MediaMeta,
|
||||
bytes: Bytes,
|
||||
) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&meta)?;
|
||||
|
||||
inner
|
||||
.media_id_media_bytes
|
||||
.insert(id.as_bytes(), bytes.as_ref())?;
|
||||
inner.media_id_media_meta.insert(id.as_bytes(), vec)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn media_id(&self, url: Url) -> Result<Option<Uuid>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.media_url_media_id.get(url.as_str().as_bytes())? {
|
||||
Ok(uuid_from_ivec(ivec))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn media_url(&self, id: Uuid) -> Result<Option<Url>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.media_id_media_url.get(id.as_bytes())? {
|
||||
Ok(url_from_ivec(ivec))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn media_bytes(&self, id: Uuid) -> Result<Option<Bytes>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.media_id_media_bytes.get(id.as_bytes())? {
|
||||
Ok(Some(Bytes::copy_from_slice(&ivec)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn media_meta(&self, id: Uuid) -> Result<Option<MediaMeta>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.media_id_media_meta.get(id.as_bytes())? {
|
||||
let meta = serde_json::from_slice(&ivec)?;
|
||||
Ok(Some(meta))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn blocks(&self) -> Result<Vec<String>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.blocks().collect())).await
|
||||
}
|
||||
|
||||
pub(crate) async fn inboxes(&self) -> Result<Vec<Url>, MyError> {
|
||||
self.unblock(|inner| Ok(inner.connected_actors().map(|actor| actor.inbox).collect()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn is_connected(&self, id: Url) -> Result<bool, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let connected = inner
|
||||
.connected_actor_ids
|
||||
.contains_key(id.as_str().as_bytes())?;
|
||||
|
||||
Ok(connected)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn actor_id_from_public_key_id(
|
||||
&self,
|
||||
public_key_id: Url,
|
||||
) -> Result<Option<Url>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner
|
||||
.public_key_id_actor_id
|
||||
.get(public_key_id.as_str().as_bytes())?
|
||||
{
|
||||
Ok(url_from_ivec(ivec))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn actor(&self, actor_id: Url) -> Result<Option<Actor>, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(ivec) = inner.actor_id_actor.get(actor_id.as_str().as_bytes())? {
|
||||
let actor = serde_json::from_slice(&ivec)?;
|
||||
Ok(Some(actor))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_actor(&self, actor: Actor) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&actor)?;
|
||||
|
||||
inner.public_key_id_actor_id.insert(
|
||||
actor.public_key_id.as_str().as_bytes(),
|
||||
actor.id.as_str().as_bytes(),
|
||||
)?;
|
||||
inner
|
||||
.actor_id_actor
|
||||
.insert(actor.id.as_str().as_bytes(), vec)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_listener(&self, actor_id: Url) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
inner
|
||||
.connected_actor_ids
|
||||
.remove(actor_id.as_str().as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn add_listener(&self, actor_id: Url) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
inner
|
||||
.connected_actor_ids
|
||||
.insert(actor_id.as_str().as_bytes(), actor_id.as_str().as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn add_blocks(&self, domains: Vec<String>) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
for connected in inner.connected_by_domain(&domains) {
|
||||
inner
|
||||
.connected_actor_ids
|
||||
.remove(connected.as_str().as_bytes())?;
|
||||
}
|
||||
|
||||
for domain in &domains {
|
||||
inner
|
||||
.blocked_domains
|
||||
.insert(domain_key(domain), domain.as_bytes())?;
|
||||
inner.allowed_domains.remove(domain_key(domain))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_blocks(&self, domains: Vec<String>) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
for domain in &domains {
|
||||
inner.blocked_domains.remove(domain_key(domain))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn add_allows(&self, domains: Vec<String>) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
for domain in &domains {
|
||||
inner
|
||||
.allowed_domains
|
||||
.insert(domain_key(domain), domain.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_allows(&self, domains: Vec<String>) -> Result<(), MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if inner.restricted_mode {
|
||||
for connected in inner.connected_by_domain(&domains) {
|
||||
inner
|
||||
.connected_actor_ids
|
||||
.remove(connected.as_str().as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
for domain in &domains {
|
||||
inner.allowed_domains.remove(domain_key(domain))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn is_allowed(&self, url: Url) -> Result<bool, MyError> {
|
||||
self.unblock(move |inner| {
|
||||
if let Some(domain) = url.domain() {
|
||||
Ok(inner.is_allowed(domain))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn private_key(&self) -> Result<Option<RSAPrivateKey>, MyError> {
|
||||
self.unblock(|inner| {
|
||||
if let Some(ivec) = inner.settings.get("private-key")? {
|
||||
let key_str = String::from_utf8_lossy(&ivec);
|
||||
let key = RSAPrivateKey::from_pem_pkcs8(&key_str)?;
|
||||
|
||||
Ok(Some(key))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn update_private_key(
|
||||
&self,
|
||||
private_key: &RSAPrivateKey,
|
||||
) -> Result<(), MyError> {
|
||||
let pem_pkcs8 = private_key.to_pem_pkcs8()?;
|
||||
|
||||
self.unblock(move |inner| {
|
||||
inner
|
||||
.settings
|
||||
.insert("private-key".as_bytes(), pem_pkcs8.as_bytes())?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn domain_key(domain: &str) -> String {
|
||||
domain.split('.').rev().collect::<Vec<_>>().join(".") + "."
|
||||
}
|
||||
|
||||
fn domain_prefix(domain: &str) -> String {
|
||||
domain
|
||||
.split('.')
|
||||
.rev()
|
||||
.take(2)
|
||||
.collect::<Vec<_>>()
|
||||
.join(".")
|
||||
+ "."
|
||||
}
|
||||
|
||||
fn url_from_ivec(ivec: sled::IVec) -> Option<Url> {
|
||||
String::from_utf8_lossy(&ivec).parse::<Url>().ok()
|
||||
}
|
||||
|
||||
fn uuid_from_ivec(ivec: sled::IVec) -> Option<Uuid> {
|
||||
Uuid::from_slice(&ivec).ok()
|
||||
}
|
||||
|
|
36
src/error.rs
36
src/error.rs
|
@ -4,7 +4,6 @@ use actix_web::{
|
|||
http::StatusCode,
|
||||
HttpResponse,
|
||||
};
|
||||
use deadpool::managed::{PoolError, TimeoutType};
|
||||
use http_signature_normalization_actix::PrepareSignError;
|
||||
use log::error;
|
||||
use rsa_pem::KeyError;
|
||||
|
@ -18,9 +17,6 @@ pub enum MyError {
|
|||
#[error("Error in configuration, {0}")]
|
||||
Config(#[from] config::ConfigError),
|
||||
|
||||
#[error("Error in db, {0}")]
|
||||
DbError(#[from] tokio_postgres::error::Error),
|
||||
|
||||
#[error("Couldn't parse key, {0}")]
|
||||
Key(#[from] KeyError),
|
||||
|
||||
|
@ -33,6 +29,9 @@ pub enum MyError {
|
|||
#[error("Couldn't sign string, {0}")]
|
||||
Rsa(rsa::errors::Error),
|
||||
|
||||
#[error("Couldn't use db, {0}")]
|
||||
Sled(#[from] sled::Error),
|
||||
|
||||
#[error("Couldn't do the json thing, {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
|
@ -48,11 +47,8 @@ pub enum MyError {
|
|||
#[error("Actor ({0}), or Actor's server, is not subscribed")]
|
||||
NotSubscribed(String),
|
||||
|
||||
#[error("Actor is blocked, {0}")]
|
||||
Blocked(String),
|
||||
|
||||
#[error("Actor is not whitelisted, {0}")]
|
||||
Whitelist(String),
|
||||
#[error("Actor is not allowed, {0}")]
|
||||
NotAllowed(String),
|
||||
|
||||
#[error("Cannot make decisions for foreign actor, {0}")]
|
||||
WrongActor(String),
|
||||
|
@ -78,9 +74,6 @@ pub enum MyError {
|
|||
#[error("Couldn't flush buffer")]
|
||||
FlushBuffer,
|
||||
|
||||
#[error("Timed out while waiting on db pool, {0:?}")]
|
||||
DbTimeout(TimeoutType),
|
||||
|
||||
#[error("Invalid algorithm provided to verifier, {0}")]
|
||||
Algorithm(String),
|
||||
|
||||
|
@ -127,10 +120,9 @@ pub enum MyError {
|
|||
impl ResponseError for MyError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
MyError::Blocked(_)
|
||||
| MyError::Whitelist(_)
|
||||
| MyError::WrongActor(_)
|
||||
| MyError::BadActor(_, _) => StatusCode::FORBIDDEN,
|
||||
MyError::NotAllowed(_) | MyError::WrongActor(_) | MyError::BadActor(_, _) => {
|
||||
StatusCode::FORBIDDEN
|
||||
}
|
||||
MyError::NotSubscribed(_) => StatusCode::UNAUTHORIZED,
|
||||
MyError::Duplicate => StatusCode::ACCEPTED,
|
||||
MyError::Kind(_) | MyError::MissingKind | MyError::MissingId | MyError::ObjectCount => {
|
||||
|
@ -161,18 +153,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<PoolError<T>> for MyError
|
||||
where
|
||||
T: Into<MyError>,
|
||||
{
|
||||
fn from(e: PoolError<T>) -> Self {
|
||||
match e {
|
||||
PoolError::Backend(e) => e.into(),
|
||||
PoolError::Timeout(t) => MyError::DbTimeout(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Infallible> for MyError {
|
||||
fn from(i: Infallible) -> Self {
|
||||
match i {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
config::{Config, UrlKind},
|
||||
data::Actor,
|
||||
db::Actor,
|
||||
error::MyError,
|
||||
jobs::{
|
||||
apub::{get_inboxes, prepare_activity},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
apub::AcceptedActivities,
|
||||
config::{Config, UrlKind},
|
||||
data::Actor,
|
||||
db::Actor,
|
||||
error::MyError,
|
||||
jobs::{apub::prepare_activity, Deliver, JobState},
|
||||
};
|
||||
|
@ -36,14 +36,16 @@ impl Follow {
|
|||
let my_id = state.config.generate_url(UrlKind::Actor);
|
||||
|
||||
// if following relay directly, not just following 'public', followback
|
||||
if self.input.object_is(&my_id) && !state.actors.is_following(&self.actor.id).await {
|
||||
if self.input.object_is(&my_id)
|
||||
&& !state.state.db.is_connected(self.actor.id.clone()).await?
|
||||
{
|
||||
let follow = generate_follow(&state.config, &self.actor.id, &my_id)?;
|
||||
state
|
||||
.job_server
|
||||
.queue(Deliver::new(self.actor.inbox.clone(), follow)?)?;
|
||||
}
|
||||
|
||||
state.actors.follower(&self.actor).await?;
|
||||
state.actors.follower(self.actor.clone()).await?;
|
||||
|
||||
let accept = generate_accept_follow(
|
||||
&state.config,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
apub::AcceptedActivities,
|
||||
data::Actor,
|
||||
db::Actor,
|
||||
error::MyError,
|
||||
jobs::{apub::get_inboxes, DeliverMany, JobState},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
config::{Config, UrlKind},
|
||||
data::{Actor, State},
|
||||
data::State,
|
||||
db::Actor,
|
||||
error::MyError,
|
||||
};
|
||||
use activitystreams::{
|
||||
|
@ -23,7 +24,7 @@ pub use self::{announce::Announce, follow::Follow, forward::Forward, reject::Rej
|
|||
async fn get_inboxes(state: &State, actor: &Actor, object_id: &Url) -> Result<Vec<Url>, MyError> {
|
||||
let domain = object_id.host().ok_or(MyError::Domain)?.to_string();
|
||||
|
||||
Ok(state.listeners_without(&actor.inbox, &domain).await)
|
||||
state.inboxes_without(&actor.inbox, &domain).await
|
||||
}
|
||||
|
||||
fn prepare_activity<T, U, V, Kind>(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
config::UrlKind,
|
||||
data::Actor,
|
||||
db::Actor,
|
||||
jobs::{apub::generate_undo_follow, Deliver, JobState},
|
||||
};
|
||||
use background_jobs::ActixJob;
|
||||
|
@ -11,9 +11,7 @@ pub struct Reject(pub Actor);
|
|||
|
||||
impl Reject {
|
||||
async fn perform(self, state: JobState) -> Result<(), anyhow::Error> {
|
||||
if state.actors.unfollower(&self.0).await?.is_some() {
|
||||
state.db.remove_listener(self.0.inbox.clone()).await?;
|
||||
}
|
||||
state.actors.unfollower(&self.0).await?;
|
||||
|
||||
let my_id = state.config.generate_url(UrlKind::Actor);
|
||||
let undo = generate_undo_follow(&state.config, &self.0.id, &my_id)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
apub::AcceptedActivities,
|
||||
config::UrlKind,
|
||||
data::Actor,
|
||||
db::Actor,
|
||||
jobs::{apub::generate_undo_follow, Deliver, JobState},
|
||||
};
|
||||
use background_jobs::ActixJob;
|
||||
|
@ -19,11 +19,9 @@ impl Undo {
|
|||
}
|
||||
|
||||
async fn perform(self, state: JobState) -> Result<(), anyhow::Error> {
|
||||
let was_following = state.actors.is_following(&self.actor.id).await;
|
||||
let was_following = state.state.db.is_connected(self.actor.id.clone()).await?;
|
||||
|
||||
if state.actors.unfollower(&self.actor).await?.is_some() {
|
||||
state.db.remove_listener(self.actor.inbox.clone()).await?;
|
||||
}
|
||||
state.actors.unfollower(&self.actor).await?;
|
||||
|
||||
if was_following {
|
||||
let my_id = state.config.generate_url(UrlKind::Actor);
|
||||
|
|
|
@ -15,7 +15,7 @@ impl CacheMedia {
|
|||
}
|
||||
|
||||
async fn perform(self, state: JobState) -> Result<(), Error> {
|
||||
if state.media.get_bytes(self.uuid).await.is_some() {
|
||||
if !state.media.is_outdated(self.uuid).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ impl CacheMedia {
|
|||
state
|
||||
.media
|
||||
.store_bytes(self.uuid, content_type, bytes)
|
||||
.await;
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -5,32 +5,35 @@ use crate::{
|
|||
use activitystreams::url::Url;
|
||||
use anyhow::Error;
|
||||
use background_jobs::ActixJob;
|
||||
use futures::join;
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct QueryInstance {
|
||||
listener: Url,
|
||||
actor_id: Url,
|
||||
}
|
||||
|
||||
impl QueryInstance {
|
||||
pub fn new(listener: Url) -> Self {
|
||||
pub fn new(actor_id: Url) -> Self {
|
||||
QueryInstance {
|
||||
listener: listener.into(),
|
||||
actor_id: actor_id.into(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn perform(self, state: JobState) -> Result<(), Error> {
|
||||
let (o1, o2) = join!(
|
||||
state.node_cache.is_contact_outdated(&self.listener),
|
||||
state.node_cache.is_instance_outdated(&self.listener),
|
||||
);
|
||||
let contact_outdated = state
|
||||
.node_cache
|
||||
.is_contact_outdated(self.actor_id.clone())
|
||||
.await;
|
||||
let instance_outdated = state
|
||||
.node_cache
|
||||
.is_instance_outdated(self.actor_id.clone())
|
||||
.await;
|
||||
|
||||
if !(o1 || o2) {
|
||||
if !(contact_outdated || instance_outdated) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut instance_uri = self.listener.clone();
|
||||
let mut instance_uri = self.actor_id.clone();
|
||||
instance_uri.set_fragment(None);
|
||||
instance_uri.set_query(None);
|
||||
instance_uri.set_path("api/v1/instance");
|
||||
|
@ -47,11 +50,11 @@ impl QueryInstance {
|
|||
};
|
||||
|
||||
if let Some(mut contact) = instance.contact {
|
||||
let uuid = if let Some(uuid) = state.media.get_uuid(&contact.avatar).await? {
|
||||
let uuid = if let Some(uuid) = state.media.get_uuid(contact.avatar.clone()).await? {
|
||||
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).into();
|
||||
uuid
|
||||
} else {
|
||||
let uuid = state.media.store_url(&contact.avatar).await?;
|
||||
let uuid = state.media.store_url(contact.avatar.clone()).await?;
|
||||
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).into();
|
||||
uuid
|
||||
};
|
||||
|
@ -61,7 +64,7 @@ impl QueryInstance {
|
|||
state
|
||||
.node_cache
|
||||
.set_contact(
|
||||
&self.listener,
|
||||
self.actor_id.clone(),
|
||||
contact.username,
|
||||
contact.display_name,
|
||||
contact.url,
|
||||
|
@ -75,7 +78,7 @@ impl QueryInstance {
|
|||
state
|
||||
.node_cache
|
||||
.set_instance(
|
||||
&self.listener,
|
||||
self.actor_id.clone(),
|
||||
instance.title,
|
||||
description,
|
||||
instance.version,
|
||||
|
|
|
@ -13,7 +13,7 @@ pub use self::{
|
|||
|
||||
use crate::{
|
||||
config::Config,
|
||||
data::{ActorCache, Media, NodeCache, State},
|
||||
data::{ActorCache, MediaCache, NodeCache, State},
|
||||
db::Db,
|
||||
error::MyError,
|
||||
jobs::process_listeners::Listeners,
|
||||
|
@ -35,7 +35,7 @@ pub fn create_workers(
|
|||
state: State,
|
||||
actors: ActorCache,
|
||||
job_server: JobServer,
|
||||
media: Media,
|
||||
media: MediaCache,
|
||||
config: Config,
|
||||
) {
|
||||
let remote_handle = job_server.remote.clone();
|
||||
|
@ -72,7 +72,7 @@ pub struct JobState {
|
|||
state: State,
|
||||
actors: ActorCache,
|
||||
config: Config,
|
||||
media: Media,
|
||||
media: MediaCache,
|
||||
node_cache: NodeCache,
|
||||
job_server: JobServer,
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ impl JobState {
|
|||
state: State,
|
||||
actors: ActorCache,
|
||||
job_server: JobServer,
|
||||
media: Media,
|
||||
media: MediaCache,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
JobState {
|
||||
|
|
|
@ -6,20 +6,24 @@ use std::{future::Future, pin::Pin};
|
|||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct QueryNodeinfo {
|
||||
listener: Url,
|
||||
actor_id: Url,
|
||||
}
|
||||
|
||||
impl QueryNodeinfo {
|
||||
pub fn new(listener: Url) -> Self {
|
||||
QueryNodeinfo { listener }
|
||||
pub fn new(actor_id: Url) -> Self {
|
||||
QueryNodeinfo { actor_id }
|
||||
}
|
||||
|
||||
async fn perform(self, state: JobState) -> Result<(), Error> {
|
||||
if !state.node_cache.is_nodeinfo_outdated(&self.listener).await {
|
||||
if !state
|
||||
.node_cache
|
||||
.is_nodeinfo_outdated(self.actor_id.clone())
|
||||
.await
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut well_known_uri = self.listener.clone();
|
||||
let mut well_known_uri = self.actor_id.clone();
|
||||
well_known_uri.set_fragment(None);
|
||||
well_known_uri.set_query(None);
|
||||
well_known_uri.set_path(".well-known/nodeinfo");
|
||||
|
@ -40,7 +44,7 @@ impl QueryNodeinfo {
|
|||
state
|
||||
.node_cache
|
||||
.set_info(
|
||||
&self.listener,
|
||||
self.actor_id.clone(),
|
||||
nodeinfo.software.name,
|
||||
nodeinfo.software.version,
|
||||
nodeinfo.open_registrations,
|
||||
|
|
|
@ -8,11 +8,11 @@ pub struct Listeners;
|
|||
|
||||
impl Listeners {
|
||||
async fn perform(self, state: JobState) -> Result<(), Error> {
|
||||
for listener in state.state.listeners().await {
|
||||
for actor_id in state.state.db.connected_ids().await? {
|
||||
state
|
||||
.job_server
|
||||
.queue(QueryInstance::new(listener.clone()))?;
|
||||
state.job_server.queue(QueryNodeinfo::new(listener))?;
|
||||
.queue(QueryInstance::new(actor_id.clone()))?;
|
||||
state.job_server.queue(QueryNodeinfo::new(actor_id))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -1,4 +1,3 @@
|
|||
use actix_rt::Arbiter;
|
||||
use actix_web::{
|
||||
middleware::{Compress, Logger},
|
||||
web, App, HttpServer,
|
||||
|
@ -12,14 +11,13 @@ mod db;
|
|||
mod error;
|
||||
mod jobs;
|
||||
mod middleware;
|
||||
mod notify;
|
||||
mod requests;
|
||||
mod routes;
|
||||
|
||||
use self::{
|
||||
args::Args,
|
||||
config::Config,
|
||||
data::{ActorCache, Media, State},
|
||||
data::{ActorCache, MediaCache, State},
|
||||
db::Db,
|
||||
jobs::{create_server, create_workers},
|
||||
middleware::{DebugPayload, RelayResolver},
|
||||
|
@ -35,7 +33,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
if config.debug() {
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
"debug,tokio_postgres=info,h2=info,trust_dns_resolver=info,trust_dns_proto=info,rustls=info,html5ever=info",
|
||||
"debug,h2=info,trust_dns_resolver=info,trust_dns_proto=info,rustls=info,html5ever=info",
|
||||
)
|
||||
} else {
|
||||
std::env::set_var("RUST_LOG", "info")
|
||||
|
@ -51,73 +49,33 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
|
||||
let args = Args::new();
|
||||
|
||||
if args.jobs_only() && args.no_jobs() {
|
||||
return Err(anyhow::Error::msg(
|
||||
"Either the server or the jobs must be run",
|
||||
));
|
||||
}
|
||||
|
||||
if !args.blocks().is_empty() || !args.whitelists().is_empty() {
|
||||
if !args.blocks().is_empty() || !args.allowed().is_empty() {
|
||||
if args.undo() {
|
||||
db.remove_blocks(args.blocks()).await?;
|
||||
db.remove_whitelists(args.whitelists()).await?;
|
||||
db.remove_blocks(args.blocks().to_vec()).await?;
|
||||
db.remove_allows(args.allowed().to_vec()).await?;
|
||||
} else {
|
||||
db.add_blocks(args.blocks()).await?;
|
||||
db.add_whitelists(args.whitelists()).await?;
|
||||
db.add_blocks(args.blocks().to_vec()).await?;
|
||||
db.add_allows(args.allowed().to_vec()).await?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let media = Media::new(db.clone());
|
||||
let state = State::hydrate(config.clone(), &db).await?;
|
||||
let media = MediaCache::new(db.clone());
|
||||
let state = State::build(config.clone(), db.clone()).await?;
|
||||
let actors = ActorCache::new(db.clone());
|
||||
let job_server = create_server();
|
||||
|
||||
notify::Notifier::new(config.database_url().parse()?)
|
||||
.register(notify::NewBlocks(state.clone()))
|
||||
.register(notify::NewWhitelists(state.clone()))
|
||||
.register(notify::NewListeners(state.clone(), job_server.clone()))
|
||||
.register(notify::NewActors(actors.clone()))
|
||||
.register(notify::NewNodes(state.node_cache()))
|
||||
.register(notify::RmBlocks(state.clone()))
|
||||
.register(notify::RmWhitelists(state.clone()))
|
||||
.register(notify::RmListeners(state.clone()))
|
||||
.register(notify::RmActors(actors.clone()))
|
||||
.register(notify::RmNodes(state.node_cache()))
|
||||
.start();
|
||||
|
||||
if args.jobs_only() {
|
||||
for _ in 0..num_cpus::get() {
|
||||
let state = state.clone();
|
||||
let actors = actors.clone();
|
||||
let job_server = job_server.clone();
|
||||
let media = media.clone();
|
||||
let config = config.clone();
|
||||
let db = db.clone();
|
||||
|
||||
Arbiter::new().exec_fn(move || {
|
||||
create_workers(db, state, actors, job_server, media, config);
|
||||
});
|
||||
}
|
||||
actix_rt::signal::ctrl_c().await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let no_jobs = args.no_jobs();
|
||||
create_workers(
|
||||
db.clone(),
|
||||
state.clone(),
|
||||
actors.clone(),
|
||||
job_server.clone(),
|
||||
media.clone(),
|
||||
config.clone(),
|
||||
);
|
||||
|
||||
let bind_address = config.bind_address();
|
||||
HttpServer::new(move || {
|
||||
if !no_jobs {
|
||||
create_workers(
|
||||
db.clone(),
|
||||
state.clone(),
|
||||
actors.clone(),
|
||||
job_server.clone(),
|
||||
media.clone(),
|
||||
config.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
App::new()
|
||||
.wrap(Compress::default())
|
||||
.wrap(Logger::default())
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::{
|
||||
apub::AcceptedActors,
|
||||
data::{ActorCache, State},
|
||||
error::MyError,
|
||||
requests::Requests,
|
||||
};
|
||||
use activitystreams::uri;
|
||||
use activitystreams::{base::BaseExt, uri, url::Url};
|
||||
use actix_web::web;
|
||||
use futures::join;
|
||||
use http_signature_normalization_actix::{prelude::*, verify::DeprecatedAlgorithm};
|
||||
use log::error;
|
||||
use rsa::{hash::Hash, padding::PaddingScheme, PublicKey, RSAPublicKey};
|
||||
|
@ -24,47 +24,55 @@ impl MyVerify {
|
|||
signature: String,
|
||||
signing_string: String,
|
||||
) -> Result<bool, MyError> {
|
||||
let mut uri = uri!(key_id);
|
||||
let public_key_id = uri!(key_id);
|
||||
|
||||
let (is_blocked, is_whitelisted) =
|
||||
join!(self.2.is_blocked(&uri), self.2.is_whitelisted(&uri));
|
||||
|
||||
if is_blocked {
|
||||
return Err(MyError::Blocked(key_id));
|
||||
}
|
||||
|
||||
if !is_whitelisted {
|
||||
return Err(MyError::Whitelist(key_id));
|
||||
}
|
||||
|
||||
uri.set_fragment(None);
|
||||
let actor = self.1.get(&uri, &self.0).await?;
|
||||
let was_cached = actor.is_cached();
|
||||
let actor = actor.into_inner();
|
||||
|
||||
match algorithm {
|
||||
Some(Algorithm::Hs2019) => (),
|
||||
Some(Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
|
||||
Some(other) => {
|
||||
return Err(MyError::Algorithm(other.to_string()));
|
||||
let actor_id = if let Some(mut actor_id) = self
|
||||
.2
|
||||
.db
|
||||
.actor_id_from_public_key_id(public_key_id.clone())
|
||||
.await?
|
||||
{
|
||||
if !self.2.db.is_allowed(actor_id.clone()).await? {
|
||||
return Err(MyError::NotAllowed(key_id));
|
||||
}
|
||||
None => (),
|
||||
};
|
||||
|
||||
let res = do_verify(&actor.public_key, signature.clone(), signing_string.clone()).await;
|
||||
actor_id.set_fragment(None);
|
||||
let actor = self.1.get(&actor_id, &self.0).await?;
|
||||
let was_cached = actor.is_cached();
|
||||
let actor = actor.into_inner();
|
||||
|
||||
if let Err(e) = res {
|
||||
if !was_cached {
|
||||
return Err(e);
|
||||
match algorithm {
|
||||
Some(Algorithm::Hs2019) => (),
|
||||
Some(Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
|
||||
Some(other) => {
|
||||
return Err(MyError::Algorithm(other.to_string()));
|
||||
}
|
||||
None => (),
|
||||
};
|
||||
|
||||
let res = do_verify(&actor.public_key, signature.clone(), signing_string.clone()).await;
|
||||
|
||||
if let Err(e) = res {
|
||||
if !was_cached {
|
||||
return Err(e);
|
||||
}
|
||||
} else {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
actor_id
|
||||
} else {
|
||||
return Ok(true);
|
||||
}
|
||||
self.0
|
||||
.fetch_json::<PublicKeyResponse>(public_key_id.as_str())
|
||||
.await?
|
||||
.actor_id()
|
||||
.ok_or_else(|| MyError::MissingId)?
|
||||
};
|
||||
|
||||
// Previously we verified the sig from an actor's local cache
|
||||
//
|
||||
// Now we make sure we fetch an updated actor
|
||||
let actor = self.1.get_no_cache(&uri, &self.0).await?;
|
||||
let actor = self.1.get_no_cache(&actor_id, &self.0).await?;
|
||||
|
||||
do_verify(&actor.public_key, signature, signing_string).await?;
|
||||
|
||||
|
@ -72,6 +80,29 @@ impl MyVerify {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum PublicKeyResponse {
|
||||
PublicKey {
|
||||
#[allow(dead_code)]
|
||||
id: Url,
|
||||
owner: Url,
|
||||
#[allow(dead_code)]
|
||||
public_key_pem: String,
|
||||
},
|
||||
Actor(AcceptedActors),
|
||||
}
|
||||
|
||||
impl PublicKeyResponse {
|
||||
fn actor_id(&self) -> Option<Url> {
|
||||
match self {
|
||||
PublicKeyResponse::PublicKey { owner, .. } => Some(owner.clone()),
|
||||
PublicKeyResponse::Actor(actor) => actor.id_unchecked().map(|url| url.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_verify(
|
||||
public_key: &str,
|
||||
signature: String,
|
||||
|
|
263
src/notify.rs
263
src/notify.rs
|
@ -1,263 +0,0 @@
|
|||
use crate::{
|
||||
data::{ActorCache, NodeCache, State},
|
||||
db::listen,
|
||||
jobs::{JobServer, QueryInstance, QueryNodeinfo},
|
||||
};
|
||||
use activitystreams::url::Url;
|
||||
use actix_rt::{spawn, time::delay_for};
|
||||
use futures::stream::{poll_fn, StreamExt};
|
||||
use log::{debug, error, warn};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio_postgres::{tls::NoTls, AsyncMessage, Config};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub trait Listener {
|
||||
fn key(&self) -> &str;
|
||||
|
||||
fn execute(&self, payload: &str);
|
||||
}
|
||||
|
||||
pub struct Notifier {
|
||||
config: Config,
|
||||
listeners: HashMap<String, Vec<Box<dyn Listener + Send + Sync + 'static>>>,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(config: Config) -> Self {
|
||||
Notifier {
|
||||
config,
|
||||
listeners: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<L>(mut self, l: L) -> Self
|
||||
where
|
||||
L: Listener + Send + Sync + 'static,
|
||||
{
|
||||
let v = self
|
||||
.listeners
|
||||
.entry(l.key().to_owned())
|
||||
.or_insert_with(Vec::new);
|
||||
v.push(Box::new(l));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn start(self) {
|
||||
spawn(async move {
|
||||
let Notifier { config, listeners } = self;
|
||||
|
||||
loop {
|
||||
let (new_client, mut conn) = match config.connect(NoTls).await {
|
||||
Ok((client, conn)) => (client, conn),
|
||||
Err(e) => {
|
||||
error!("Error establishing DB Connection, {}", e);
|
||||
delay_for(Duration::new(5, 0)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let client = Arc::new(new_client);
|
||||
let new_client = client.clone();
|
||||
|
||||
spawn(async move {
|
||||
if let Err(e) = listen(&new_client).await {
|
||||
error!("Error listening for updates, {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let mut stream = poll_fn(move |cx| conn.poll_message(cx));
|
||||
|
||||
loop {
|
||||
match stream.next().await {
|
||||
Some(Ok(AsyncMessage::Notification(n))) => {
|
||||
debug!("Handling Notification, {:?}", n);
|
||||
if let Some(v) = listeners.get(n.channel()) {
|
||||
for l in v {
|
||||
l.execute(n.payload());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Ok(AsyncMessage::Notice(e))) => {
|
||||
debug!("Handling Notice, {:?}", e);
|
||||
}
|
||||
Some(Ok(_)) => {
|
||||
debug!("Handling rest");
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
debug!("Breaking loop due to error Error, {:?}", e);
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
debug!("End of stream, breaking loop");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(client);
|
||||
warn!("Restarting listener task");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NewBlocks(pub State);
|
||||
pub struct NewWhitelists(pub State);
|
||||
pub struct NewListeners(pub State, pub JobServer);
|
||||
pub struct NewActors(pub ActorCache);
|
||||
pub struct NewNodes(pub NodeCache);
|
||||
pub struct RmBlocks(pub State);
|
||||
pub struct RmWhitelists(pub State);
|
||||
pub struct RmListeners(pub State);
|
||||
pub struct RmActors(pub ActorCache);
|
||||
pub struct RmNodes(pub NodeCache);
|
||||
|
||||
impl Listener for NewBlocks {
|
||||
fn key(&self) -> &str {
|
||||
"new_blocks"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
debug!("Caching block of {}", payload);
|
||||
let state = self.0.clone();
|
||||
let payload = payload.to_owned();
|
||||
spawn(async move { state.cache_block(payload).await });
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for NewWhitelists {
|
||||
fn key(&self) -> &str {
|
||||
"new_whitelists"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
debug!("Caching whitelist of {}", payload);
|
||||
let state = self.0.clone();
|
||||
let payload = payload.to_owned();
|
||||
spawn(async move { state.cache_whitelist(payload.to_owned()).await });
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for NewListeners {
|
||||
fn key(&self) -> &str {
|
||||
"new_listeners"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uri) = payload.parse::<Url>() {
|
||||
debug!("Caching listener {}", uri);
|
||||
let state = self.0.clone();
|
||||
let _ = self.1.queue(QueryInstance::new(uri.clone()));
|
||||
let _ = self.1.queue(QueryNodeinfo::new(uri.clone()));
|
||||
spawn(async move { state.cache_listener(uri).await });
|
||||
} else {
|
||||
warn!("Not caching listener {}, parse error", payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for NewActors {
|
||||
fn key(&self) -> &str {
|
||||
"new_actors"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uri) = payload.parse::<Url>() {
|
||||
debug!("Caching actor {}", uri);
|
||||
let actors = self.0.clone();
|
||||
spawn(async move { actors.cache_follower(uri).await });
|
||||
} else {
|
||||
warn!("Not caching actor {}, parse error", payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for NewNodes {
|
||||
fn key(&self) -> &str {
|
||||
"new_nodes"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uuid) = payload.parse::<Uuid>() {
|
||||
debug!("Caching node {}", uuid);
|
||||
let nodes = self.0.clone();
|
||||
spawn(async move { nodes.cache_by_id(uuid).await });
|
||||
} else {
|
||||
warn!("Not caching node {}, parse error", payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for RmBlocks {
|
||||
fn key(&self) -> &str {
|
||||
"rm_blocks"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
debug!("Busting block cache for {}", payload);
|
||||
let state = self.0.clone();
|
||||
let payload = payload.to_owned();
|
||||
spawn(async move { state.bust_block(&payload).await });
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for RmWhitelists {
|
||||
fn key(&self) -> &str {
|
||||
"rm_whitelists"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
debug!("Busting whitelist cache for {}", payload);
|
||||
let state = self.0.clone();
|
||||
let payload = payload.to_owned();
|
||||
spawn(async move { state.bust_whitelist(&payload).await });
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for RmListeners {
|
||||
fn key(&self) -> &str {
|
||||
"rm_listeners"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uri) = payload.parse::<Url>() {
|
||||
debug!("Busting listener cache for {}", uri);
|
||||
let state = self.0.clone();
|
||||
spawn(async move { state.bust_listener(&uri).await });
|
||||
} else {
|
||||
warn!("Not busting listener cache for {}", payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for RmActors {
|
||||
fn key(&self) -> &str {
|
||||
"rm_actors"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uri) = payload.parse::<Url>() {
|
||||
debug!("Busting actor cache for {}", uri);
|
||||
let actors = self.0.clone();
|
||||
spawn(async move { actors.bust_follower(&uri).await });
|
||||
} else {
|
||||
warn!("Not busting actor cache for {}", payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener for RmNodes {
|
||||
fn key(&self) -> &str {
|
||||
"rm_nodes"
|
||||
}
|
||||
|
||||
fn execute(&self, payload: &str) {
|
||||
if let Ok(uuid) = payload.parse::<Uuid>() {
|
||||
debug!("Caching node {}", uuid);
|
||||
let nodes = self.0.clone();
|
||||
spawn(async move { nodes.bust_by_id(uuid).await });
|
||||
} else {
|
||||
warn!("Not caching node {}, parse error", payload);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
apub::{AcceptedActivities, AcceptedUndoObjects, UndoTypes, ValidTypes},
|
||||
config::{Config, UrlKind},
|
||||
data::{Actor, ActorCache, State},
|
||||
data::{ActorCache, State},
|
||||
db::Actor,
|
||||
error::MyError,
|
||||
jobs::apub::{Announce, Follow, Forward, Reject, Undo},
|
||||
jobs::JobServer,
|
||||
|
@ -12,7 +13,6 @@ use activitystreams::{
|
|||
activity, base::AnyBase, prelude::*, primitives::OneOrMany, public, url::Url,
|
||||
};
|
||||
use actix_web::{web, HttpResponse};
|
||||
use futures::join;
|
||||
use http_signature_normalization_actix::prelude::{DigestVerified, SignatureVerified};
|
||||
use log::error;
|
||||
|
||||
|
@ -35,21 +35,14 @@ pub async fn route(
|
|||
.await?
|
||||
.into_inner();
|
||||
|
||||
let (is_blocked, is_whitelisted, is_listener) = join!(
|
||||
state.is_blocked(&actor.id),
|
||||
state.is_whitelisted(&actor.id),
|
||||
state.is_listener(&actor.inbox)
|
||||
);
|
||||
let is_allowed = state.db.is_allowed(actor.id.clone()).await?;
|
||||
let is_connected = state.db.is_connected(actor.id.clone()).await?;
|
||||
|
||||
if is_blocked {
|
||||
return Err(MyError::Blocked(actor.id.to_string()));
|
||||
if !is_allowed {
|
||||
return Err(MyError::NotAllowed(actor.id.to_string()));
|
||||
}
|
||||
|
||||
if !is_whitelisted {
|
||||
return Err(MyError::Whitelist(actor.id.to_string()));
|
||||
}
|
||||
|
||||
if !is_listener && !valid_without_listener(&input)? {
|
||||
if !is_connected && !valid_without_listener(&input)? {
|
||||
return Err(MyError::NotSubscribed(actor.inbox.to_string()));
|
||||
}
|
||||
|
||||
|
@ -73,9 +66,9 @@ pub async fn route(
|
|||
ValidTypes::Announce | ValidTypes::Create => {
|
||||
handle_announce(&state, &jobs, input, actor).await?
|
||||
}
|
||||
ValidTypes::Follow => handle_follow(&config, &jobs, input, actor, is_listener).await?,
|
||||
ValidTypes::Follow => handle_follow(&config, &jobs, input, actor, is_connected).await?,
|
||||
ValidTypes::Delete | ValidTypes::Update => handle_forward(&jobs, input, actor).await?,
|
||||
ValidTypes::Undo => handle_undo(&config, &jobs, input, actor, is_listener).await?,
|
||||
ValidTypes::Undo => handle_undo(&config, &jobs, input, actor, is_connected).await?,
|
||||
};
|
||||
|
||||
Ok(accepted(serde_json::json!({})))
|
||||
|
|
|
@ -8,7 +8,7 @@ pub async fn route(
|
|||
state: web::Data<State>,
|
||||
config: web::Data<Config>,
|
||||
) -> Result<HttpResponse, MyError> {
|
||||
let mut nodes = state.node_cache().nodes().await;
|
||||
let mut nodes = state.node_cache().nodes().await?;
|
||||
nodes.shuffle(&mut thread_rng());
|
||||
let mut buf = BufWriter::new(Vec::new());
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{data::Media, error::MyError, requests::Requests};
|
||||
use crate::{data::MediaCache, error::MyError, requests::Requests};
|
||||
use actix_web::{
|
||||
http::header::{CacheControl, CacheDirective},
|
||||
web, HttpResponse,
|
||||
|
@ -6,13 +6,13 @@ use actix_web::{
|
|||
use uuid::Uuid;
|
||||
|
||||
pub async fn route(
|
||||
media: web::Data<Media>,
|
||||
media: web::Data<MediaCache>,
|
||||
requests: web::Data<Requests>,
|
||||
uuid: web::Path<Uuid>,
|
||||
) -> Result<HttpResponse, MyError> {
|
||||
let uuid = uuid.into_inner();
|
||||
|
||||
if let Some((content_type, bytes)) = media.get_bytes(uuid).await {
|
||||
if let Some((content_type, bytes)) = media.get_bytes(uuid).await? {
|
||||
return Ok(cached(content_type, bytes));
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ pub async fn route(
|
|||
|
||||
media
|
||||
.store_bytes(uuid, content_type.clone(), bytes.clone())
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
return Ok(cached(content_type, bytes));
|
||||
}
|
||||
|
|
|
@ -46,14 +46,16 @@ pub async fn route(config: web::Data<Config>, state: web::Data<State>) -> web::J
|
|||
},
|
||||
metadata: Metadata {
|
||||
peers: state
|
||||
.listeners()
|
||||
.db
|
||||
.inboxes()
|
||||
.await
|
||||
.unwrap_or(vec![])
|
||||
.iter()
|
||||
.filter_map(|listener| listener.domain())
|
||||
.map(|s| s.to_owned())
|
||||
.collect(),
|
||||
blocks: if config.publish_blocks() {
|
||||
Some(state.blocks().await)
|
||||
Some(state.db.blocks().await.unwrap_or(vec![]))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
table! {
|
||||
actors (id) {
|
||||
id -> Uuid,
|
||||
actor_id -> Text,
|
||||
public_key -> Text,
|
||||
public_key_id -> Text,
|
||||
listener_id -> Uuid,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
blocks (id) {
|
||||
id -> Uuid,
|
||||
domain_name -> Text,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
jobs (id) {
|
||||
id -> Uuid,
|
||||
job_id -> Uuid,
|
||||
job_queue -> Text,
|
||||
job_timeout -> Int8,
|
||||
job_updated -> Timestamp,
|
||||
job_status -> Text,
|
||||
job_value -> Jsonb,
|
||||
job_next_run -> Nullable<Timestamp>,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
listeners (id) {
|
||||
id -> Uuid,
|
||||
actor_id -> Text,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
media (id) {
|
||||
id -> Uuid,
|
||||
media_id -> Uuid,
|
||||
url -> Text,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
nodes (id) {
|
||||
id -> Uuid,
|
||||
listener_id -> Uuid,
|
||||
nodeinfo -> Nullable<Jsonb>,
|
||||
instance -> Nullable<Jsonb>,
|
||||
contact -> Nullable<Jsonb>,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
settings (id) {
|
||||
id -> Uuid,
|
||||
key -> Text,
|
||||
value -> Text,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
whitelists (id) {
|
||||
id -> Uuid,
|
||||
domain_name -> Text,
|
||||
created_at -> Timestamp,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(actors -> listeners (listener_id));
|
||||
joinable!(nodes -> listeners (listener_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
actors,
|
||||
blocks,
|
||||
jobs,
|
||||
listeners,
|
||||
media,
|
||||
nodes,
|
||||
settings,
|
||||
whitelists,
|
||||
);
|
|
@ -1,4 +1,4 @@
|
|||
@use crate::data::Contact;
|
||||
@use crate::db::Contact;
|
||||
@use activitystreams::url::Url;
|
||||
|
||||
@(contact: &Contact, base: &Url)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@use crate::data::Info;
|
||||
@use crate::db::Info;
|
||||
@use activitystreams::url::Url;
|
||||
|
||||
@(info: &Info, base: &Url)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@use crate::{data::{Contact, Instance}, templates::admin};
|
||||
@use crate::{db::{Contact, Instance}, templates::admin};
|
||||
@use activitystreams::url::Url;
|
||||
|
||||
@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &Url)
|
||||
|
|
Loading…
Reference in a new issue