No warnings, expose top-level methods for image uploads
This commit is contained in:
parent
397f67ade6
commit
fff74bf063
|
@ -86,7 +86,7 @@ where
|
|||
|
||||
let res = (f)(activity, context);
|
||||
if res.as_ref().map(|r| r.is_ok()).unwrap_or(false) {
|
||||
context.apub.store_object(any_base)?;
|
||||
context.apub.store_object(&any_base)?;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ use uuid::Uuid;
|
|||
|
||||
mod actions;
|
||||
mod keys;
|
||||
pub mod results;
|
||||
|
||||
pub(crate) use actions::ingest;
|
||||
use keys::ExtendedPerson;
|
||||
use keys::{ExtendedPerson, PublicKey, PublicKeyInner};
|
||||
|
||||
pub trait ApubIds {
|
||||
fn gen_id(&self) -> Url;
|
||||
|
@ -40,12 +41,11 @@ pub enum StoreError {
|
|||
pub struct Store {
|
||||
apub_id: Tree,
|
||||
id_apub: Tree,
|
||||
deleted: Tree,
|
||||
objects: Tree,
|
||||
seen: Tree,
|
||||
keys: Tree,
|
||||
profile_key: Tree,
|
||||
endpoints: Tree,
|
||||
profile_endpoint: Tree,
|
||||
info: Arc<dyn ApubIds + Send + Sync>,
|
||||
}
|
||||
|
||||
|
@ -159,24 +159,39 @@ impl Store {
|
|||
Ok(Store {
|
||||
id_apub: db.open_tree("/profiles/apub/id-apub")?,
|
||||
apub_id: db.open_tree("/profiles/apub/apub-id")?,
|
||||
deleted: db.open_tree("/profiles/apub/deleted")?,
|
||||
objects: db.open_tree("/profiles/apub/objects")?,
|
||||
seen: db.open_tree("/profiles/apub/seen")?,
|
||||
keys: db.open_tree("/profiles/apub/keys")?,
|
||||
profile_key: db.open_tree("/profiles/apub/profile/key")?,
|
||||
endpoints: db.open_tree("/profiles/apub/endpoints")?,
|
||||
profile_endpoint: db.open_tree("/profiles/apub/profile/endpoint")?,
|
||||
info: Arc::new(ids),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn key_for_profile(&self, profile_id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
fn endpoints_for_profile(&self, profile_id: Uuid) -> Result<Option<Endpoints>, StoreError> {
|
||||
let opt = self
|
||||
.profile_endpoint
|
||||
.get(profile_endpoint_id(profile_id))?
|
||||
.and_then(url_from_ivec);
|
||||
|
||||
if let Some(profile_id) = opt {
|
||||
if let Some(ivec) = self.endpoints.get(profile_id.as_str())? {
|
||||
let endpoints: Endpoints = serde_json::from_slice(&ivec)?;
|
||||
return Ok(Some(endpoints));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn key_for_profile(&self, profile_id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.profile_key
|
||||
.get(profile_key_id(profile_id))
|
||||
.map(|opt| opt.and_then(url_from_ivec))
|
||||
.map_err(StoreError::from)
|
||||
}
|
||||
|
||||
pub(crate) fn public_key_for_id(&self, key_id: &Url) -> Result<Option<String>, StoreError> {
|
||||
fn public_key_for_id(&self, key_id: &Url) -> Result<Option<String>, StoreError> {
|
||||
if let Some(ivec) = self.keys.get(key_id.as_str())? {
|
||||
let keys: Keys = serde_json::from_slice(&ivec)?;
|
||||
return Ok(Some(keys.public));
|
||||
|
@ -184,7 +199,29 @@ impl Store {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) fn store_public_key(
|
||||
fn store_endpoints(
|
||||
&self,
|
||||
profile_id: Uuid,
|
||||
apub_id: &Url,
|
||||
endpoints: Endpoints,
|
||||
) -> Result<(), StoreError> {
|
||||
let endpoints_vec = serde_json::to_vec(&endpoints)?;
|
||||
self.endpoints.insert(apub_id.as_str(), endpoints_vec)?;
|
||||
self.profile_endpoint
|
||||
.insert(profile_endpoint_id(profile_id), apub_id.as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_keys(&self, profile_id: Uuid, key_id: &Url) -> Result<String, StoreError> {
|
||||
let keys = Keys::generate()?;
|
||||
let keys_vec = serde_json::to_vec(&keys)?;
|
||||
self.keys.insert(key_id.as_str(), keys_vec)?;
|
||||
self.profile_key
|
||||
.insert(profile_key_id(profile_id), key_id.as_str())?;
|
||||
Ok(keys.public)
|
||||
}
|
||||
|
||||
fn store_public_key(
|
||||
&self,
|
||||
profile_id: Uuid,
|
||||
key_id: &Url,
|
||||
|
@ -198,7 +235,7 @@ impl Store {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn object(&self, id: &Url) -> Result<Option<AnyBase>, StoreError> {
|
||||
fn object(&self, id: &Url) -> Result<Option<AnyBase>, StoreError> {
|
||||
if let Some(ivec) = self.objects.get(id.as_str())? {
|
||||
let any_base: AnyBase = serde_json::from_slice(&ivec)?;
|
||||
return Ok(Some(any_base));
|
||||
|
@ -207,63 +244,67 @@ impl Store {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) fn store_object(&self, any_base: AnyBase) -> Result<(), StoreError> {
|
||||
fn store_object(&self, any_base: &AnyBase) -> Result<(), StoreError> {
|
||||
if let Some(id) = any_base.id() {
|
||||
let obj_vec = serde_json::to_vec(&any_base)?;
|
||||
let obj_vec = serde_json::to_vec(any_base)?;
|
||||
self.objects.insert(id.as_str(), obj_vec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_seen(&self, url: &Url) -> Result<bool, StoreError> {
|
||||
Ok(self.seen.get(url.as_str())?.is_some())
|
||||
fn apub_for_submission(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::Submission(id))
|
||||
}
|
||||
|
||||
pub(crate) fn seen(&self, url: &Url) -> Result<(), StoreError> {
|
||||
self.seen.insert(url.as_str(), url.as_str())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn delete_id(&self, id: StoredRecords) -> Result<(), StoreError> {
|
||||
if let Some(url_ivec) = self.id_apub.remove(id.to_string())? {
|
||||
self.apub_id.remove(url_ivec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn delete_apub(&self, apub_id: &Url) -> Result<(), StoreError> {
|
||||
if let Some(record_ivec) = self.apub_id.remove(apub_id.as_str())? {
|
||||
self.id_apub.remove(record_ivec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn submission(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn submission(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::Submission(id))
|
||||
}
|
||||
|
||||
pub(crate) fn profile(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_profile(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::Profile(id))
|
||||
}
|
||||
|
||||
fn profile(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::Profile(id))
|
||||
}
|
||||
|
||||
pub(crate) fn comment(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_comment(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::Comment(id))
|
||||
}
|
||||
|
||||
fn comment(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::Comment(id))
|
||||
}
|
||||
|
||||
pub(crate) fn react(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_react(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::React(id))
|
||||
}
|
||||
|
||||
fn react(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::React(id))
|
||||
}
|
||||
|
||||
pub(crate) fn follow(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_follow(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::Follow(id))
|
||||
}
|
||||
|
||||
fn follow(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::Follow(id))
|
||||
}
|
||||
|
||||
pub(crate) fn follow_request(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_follow_request(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::FollowRequest(id))
|
||||
}
|
||||
|
||||
fn follow_request(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::FollowRequest(id))
|
||||
}
|
||||
|
||||
pub(crate) fn block(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
fn apub_for_block(&self, id: Uuid) -> Result<Option<Url>, StoreError> {
|
||||
self.apub_for_id(StoredRecords::Block(id))
|
||||
}
|
||||
|
||||
fn block(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
|
||||
self.establish(apub_id, StoredRecords::Block(id))
|
||||
}
|
||||
|
||||
|
@ -275,14 +316,14 @@ impl Store {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn id_for_apub(&self, apub_id: &Url) -> Result<Option<StoredRecords>, StoreError> {
|
||||
fn id_for_apub(&self, apub_id: &Url) -> Result<Option<StoredRecords>, StoreError> {
|
||||
Ok(self
|
||||
.apub_id
|
||||
.get(apub_id.as_str())?
|
||||
.and_then(record_from_ivec))
|
||||
}
|
||||
|
||||
pub(crate) fn apub_for_id(&self, id: StoredRecords) -> Result<Option<Url>, StoreError> {
|
||||
fn apub_for_id(&self, id: StoredRecords) -> Result<Option<Url>, StoreError> {
|
||||
Ok(self.id_apub.get(id.to_string())?.and_then(url_from_ivec))
|
||||
}
|
||||
}
|
||||
|
@ -319,36 +360,25 @@ impl Keys {
|
|||
}
|
||||
}
|
||||
|
||||
fn profile_endpoint_id(profile_id: Uuid) -> String {
|
||||
format!("/profile/{}/endpoint-id", profile_id)
|
||||
}
|
||||
|
||||
fn profile_key_id(profile_id: Uuid) -> String {
|
||||
format!("/profile/{}/key-id", profile_id)
|
||||
}
|
||||
|
||||
fn record_endpoints_key(record: StoredRecords) -> String {
|
||||
format!("/object/{}/endpoints", record)
|
||||
}
|
||||
|
||||
fn record_keys_key(record: StoredRecords) -> String {
|
||||
format!("/object/{}/keys", record)
|
||||
}
|
||||
|
||||
fn object_record_key(object_id: &Url) -> String {
|
||||
format!("/object/{}/record", object_id)
|
||||
}
|
||||
|
||||
fn owner_record_key(owner_id: &Url) -> String {
|
||||
format!("/owner/{}/record", owner_id)
|
||||
}
|
||||
|
||||
fn record_object_key(record: StoredRecords) -> String {
|
||||
format!("/record/{}/object", record)
|
||||
}
|
||||
|
||||
impl fmt::Debug for Store {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Store")
|
||||
.field("relationship", &"Tree")
|
||||
.field("apub_id", &"Tree")
|
||||
.field("id_apub", &"Tree")
|
||||
.field("objects", &"Tree")
|
||||
.field("keys", &"Tree")
|
||||
.field("profile_key", &"Tree")
|
||||
.field("endpoints", &"Tree")
|
||||
.field("profile_endpoint", &"Tree")
|
||||
.field("info", &"Rc<dyn ApubIds>")
|
||||
.finish()
|
||||
}
|
||||
|
|
46
profiles/src/apub/results/block.rs
Normal file
46
profiles/src/apub/results/block.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use super::{Blocked, Unblocked};
|
||||
use crate::{Completed, Context, Error, Required};
|
||||
use activitystreams::{
|
||||
activity::{Block, Undo},
|
||||
base::AnyBase,
|
||||
context,
|
||||
prelude::*,
|
||||
security,
|
||||
};
|
||||
|
||||
impl Completed for Blocked {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let block = ctx.store.view.blocks.by_id(self.block_id)?.req()?;
|
||||
let actor_id = ctx.apub.apub_for_profile(block.right)?.req()?;
|
||||
let object_id = ctx.apub.apub_for_profile(block.left)?.req()?;
|
||||
let published = block.published;
|
||||
|
||||
let mut block = Block::new(actor_id, object_id);
|
||||
block
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.set_published(published.into())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let block = block.into_any_base()?;
|
||||
ctx.apub.store_object(&block)?;
|
||||
|
||||
Ok(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for Unblocked {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let block_id = ctx.apub.apub_for_block(self.block_id)?.req()?;
|
||||
let block = ctx.apub.object(&block_id)?.req()?;
|
||||
|
||||
let mut undo = Undo::new(person_id, block);
|
||||
undo.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let undo = undo.into_any_base()?;
|
||||
ctx.apub.store_object(&undo)?;
|
||||
|
||||
Ok(undo)
|
||||
}
|
||||
}
|
97
profiles/src/apub/results/comment.rs
Normal file
97
profiles/src/apub/results/comment.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use super::{CommentCreated, CommentDeleted, CommentUpdated};
|
||||
use crate::{store::Comment, Completed, Context, Error, Required};
|
||||
use activitystreams::{
|
||||
activity::{Create, Delete, Update},
|
||||
base::AnyBase,
|
||||
context,
|
||||
object::Note,
|
||||
prelude::*,
|
||||
public, security,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
fn build_comment(
|
||||
comment: Comment,
|
||||
note_id: Url,
|
||||
person_id: Url,
|
||||
ctx: &Context,
|
||||
) -> Result<AnyBase, Error> {
|
||||
let published = comment.published();
|
||||
let endpoints = ctx
|
||||
.apub
|
||||
.endpoints_for_profile(comment.profile_id())?
|
||||
.req()?;
|
||||
|
||||
let mut note = Note::new();
|
||||
note.set_id(note_id)
|
||||
.set_content(comment.body())
|
||||
.set_published(published.into())
|
||||
.set_attributed_to(person_id)
|
||||
.add_to(endpoints.followers)
|
||||
.add_cc(public());
|
||||
if let Some(comment_id) = comment.comment_id() {
|
||||
let in_reply_to = ctx.apub.apub_for_comment(comment_id)?.req()?;
|
||||
note.set_in_reply_to(in_reply_to);
|
||||
}
|
||||
|
||||
let any_base = note.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
|
||||
impl Completed for CommentCreated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let comment = ctx.store.comments.by_id(self.comment_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(comment.profile_id())?.req()?;
|
||||
let note_id = ctx.apub.info.gen_id();
|
||||
let note = build_comment(comment, note_id, person_id.clone(), ctx)?;
|
||||
|
||||
let mut create = Create::new(person_id, note);
|
||||
create
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = create.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for CommentUpdated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let comment = ctx.store.comments.by_id(self.comment_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(comment.profile_id())?.req()?;
|
||||
let note_id = ctx.apub.apub_for_comment(comment.id())?.req()?;
|
||||
let note = build_comment(comment, note_id, person_id.clone(), ctx)?;
|
||||
|
||||
let mut update = Update::new(person_id, note);
|
||||
update
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = update.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for CommentDeleted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let note_id = ctx.apub.apub_for_comment(self.comment_id)?.req()?;
|
||||
let note = ctx.apub.object(¬e_id)?.req()?;
|
||||
|
||||
let mut delete = Delete::new(person_id, note);
|
||||
delete
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = delete.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
136
profiles/src/apub/results/follow_request.rs
Normal file
136
profiles/src/apub/results/follow_request.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use super::{
|
||||
FollowRequestAccepted, FollowRequestDeleted, FollowRequestRejected, FollowRequested,
|
||||
UndoneFollowRequestAccepted, Unfollowed,
|
||||
};
|
||||
use crate::{Completed, Context, Error, Required};
|
||||
use activitystreams::{
|
||||
activity::{Accept, Follow, Reject, Undo},
|
||||
base::AnyBase,
|
||||
context,
|
||||
prelude::*,
|
||||
security,
|
||||
};
|
||||
|
||||
impl Completed for FollowRequested {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let follow = ctx
|
||||
.store
|
||||
.view
|
||||
.follow_requests
|
||||
.by_id(self.follow_request_id)?
|
||||
.req()?;
|
||||
let actor_id = ctx.apub.apub_for_profile(follow.right)?.req()?;
|
||||
let object_id = ctx.apub.apub_for_profile(follow.left)?.req()?;
|
||||
let published = follow.published;
|
||||
|
||||
let mut follow = Follow::new(actor_id, object_id);
|
||||
follow
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.set_published(published.into())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let follow = follow.into_any_base()?;
|
||||
ctx.apub.store_object(&follow)?;
|
||||
|
||||
Ok(follow)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for FollowRequestDeleted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let follow_request_id = ctx
|
||||
.apub
|
||||
.apub_for_follow_request(self.follow_request_id)?
|
||||
.req()?;
|
||||
let follow_request = ctx.apub.object(&follow_request_id)?.req()?;
|
||||
|
||||
let mut undo = Undo::new(person_id, follow_request);
|
||||
undo.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let undo = undo.into_any_base()?;
|
||||
ctx.apub.store_object(&undo)?;
|
||||
|
||||
Ok(undo)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for FollowRequestRejected {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let follow_request_id = ctx
|
||||
.apub
|
||||
.apub_for_follow_request(self.follow_request_id)?
|
||||
.req()?;
|
||||
let follow_request = ctx.apub.object(&follow_request_id)?.req()?;
|
||||
|
||||
let mut reject = Reject::new(person_id, follow_request);
|
||||
reject
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let reject = reject.into_any_base()?;
|
||||
ctx.apub.store_object(&reject)?;
|
||||
|
||||
Ok(reject)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for FollowRequestAccepted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let follow_request_id = ctx
|
||||
.apub
|
||||
.apub_for_follow_request(self.follow_request_id)?
|
||||
.req()?;
|
||||
let follow_request = ctx.apub.object(&follow_request_id)?.req()?;
|
||||
|
||||
let accept_id = ctx.apub.info.gen_id();
|
||||
ctx.apub.follow(&accept_id, self.follow_id)?;
|
||||
|
||||
let mut accept = Accept::new(person_id, follow_request);
|
||||
accept
|
||||
.set_id(accept_id)
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let accept = accept.into_any_base()?;
|
||||
ctx.apub.store_object(&accept)?;
|
||||
|
||||
Ok(accept)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for UndoneFollowRequestAccepted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let follow_id = ctx.apub.apub_for_follow(self.follow_id)?.req()?;
|
||||
let follow = ctx.apub.object(&follow_id)?.req()?;
|
||||
|
||||
let mut undo = Undo::new(person_id, follow);
|
||||
undo.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let undo = undo.into_any_base()?;
|
||||
ctx.apub.store_object(&undo)?;
|
||||
|
||||
Ok(undo)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for Unfollowed {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let follow_id = ctx.apub.apub_for_follow(self.follow_id)?.req()?;
|
||||
let follow = ctx.apub.object(&follow_id)?.req()?;
|
||||
|
||||
let mut undo = Undo::new(person_id, follow);
|
||||
undo.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let undo = undo.into_any_base()?;
|
||||
ctx.apub.store_object(&undo)?;
|
||||
|
||||
Ok(undo)
|
||||
}
|
||||
}
|
94
profiles/src/apub/results/mod.rs
Normal file
94
profiles/src/apub/results/mod.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
mod block;
|
||||
mod comment;
|
||||
mod follow_request;
|
||||
mod profile;
|
||||
mod react;
|
||||
mod submission;
|
||||
|
||||
pub(crate) struct ProfileCreated {
|
||||
profile_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct ProfileUpdated {
|
||||
profile_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct ProfileDeleted {
|
||||
profile_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct SubmissionCreated {
|
||||
submission_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct SubmissionUpdated {
|
||||
submission_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct SubmissionDeleted {
|
||||
profile_id: Uuid,
|
||||
submission_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct CommentCreated {
|
||||
comment_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct CommentUpdated {
|
||||
comment_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct CommentDeleted {
|
||||
profile_id: Uuid,
|
||||
comment_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct ReactCreated {
|
||||
react_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct ReactDeleted {
|
||||
profile_id: Uuid,
|
||||
react_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct FollowRequested {
|
||||
follow_request_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct FollowRequestDeleted {
|
||||
profile_id: Uuid,
|
||||
follow_request_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct FollowRequestRejected {
|
||||
profile_id: Uuid,
|
||||
follow_request_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct FollowRequestAccepted {
|
||||
profile_id: Uuid,
|
||||
follow_id: Uuid,
|
||||
follow_request_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct UndoneFollowRequestAccepted {
|
||||
profile_id: Uuid,
|
||||
follow_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct Unfollowed {
|
||||
profile_id: Uuid,
|
||||
follow_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct Blocked {
|
||||
block_id: Uuid,
|
||||
}
|
||||
|
||||
pub(crate) struct Unblocked {
|
||||
profile_id: Uuid,
|
||||
block_id: Uuid,
|
||||
}
|
185
profiles/src/apub/results/profile.rs
Normal file
185
profiles/src/apub/results/profile.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
use super::{ProfileCreated, ProfileDeleted, ProfileUpdated};
|
||||
use crate::{
|
||||
apub::{ExtendedPerson, PublicKey, PublicKeyInner},
|
||||
store::FileSource,
|
||||
Completed, Context, Error, Required,
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::{Create, Delete, Update},
|
||||
actor::{ApActor, Endpoints, Person},
|
||||
base::AnyBase,
|
||||
context,
|
||||
prelude::*,
|
||||
public, security,
|
||||
};
|
||||
|
||||
impl Completed for ProfileCreated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let profile = ctx.store.profiles.by_id(self.profile_id)?.req()?;
|
||||
|
||||
let person_id = ctx.apub.info.gen_id();
|
||||
let public_key_id = ctx.apub.info.public_key(&person_id).req()?;
|
||||
let following = ctx.apub.info.following(&person_id).req()?;
|
||||
let followers = ctx.apub.info.followers(&person_id).req()?;
|
||||
let inbox = ctx.apub.info.inbox(&person_id).req()?;
|
||||
let outbox = ctx.apub.info.outbox(&person_id).req()?;
|
||||
let shared_inbox = ctx.apub.info.shared_inbox();
|
||||
|
||||
ctx.apub.profile(&person_id, profile.id())?;
|
||||
ctx.apub.store_endpoints(
|
||||
profile.id(),
|
||||
&person_id,
|
||||
crate::apub::Endpoints {
|
||||
inbox: inbox.clone(),
|
||||
outbox: outbox.clone(),
|
||||
following: following.clone(),
|
||||
followers: followers.clone(),
|
||||
shared_inbox: shared_inbox.clone(),
|
||||
public_key: public_key_id.clone(),
|
||||
},
|
||||
)?;
|
||||
let public_key_pem = ctx.apub.gen_keys(profile.id(), &person_id)?;
|
||||
|
||||
let mut person = ExtendedPerson::new(
|
||||
ApActor::new(inbox, Person::new()),
|
||||
PublicKey {
|
||||
public_key: PublicKeyInner {
|
||||
id: public_key_id,
|
||||
owner: person_id.clone(),
|
||||
public_key_pem,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
person
|
||||
.set_id(person_id.clone())
|
||||
.set_following(following)
|
||||
.set_followers(followers)
|
||||
.set_outbox(outbox)
|
||||
.set_endpoints(Endpoints {
|
||||
shared_inbox: Some(shared_inbox),
|
||||
..Endpoints::default()
|
||||
})
|
||||
.set_preferred_username(profile.handle())
|
||||
.set_published(profile.published().into());
|
||||
|
||||
if let Some(display_name) = profile.display_name() {
|
||||
person.set_name(display_name);
|
||||
}
|
||||
if let Some(description) = profile.description() {
|
||||
person.set_summary(description);
|
||||
}
|
||||
if let Some(icon) = profile.icon() {
|
||||
let file = ctx.store.files.by_id(icon)?.req()?;
|
||||
let FileSource::PictRs(pictrs_file) = file.source();
|
||||
person.set_icon(ctx.pictrs.image_url(pictrs_file.key()));
|
||||
}
|
||||
if let Some(banner) = profile.banner() {
|
||||
let file = ctx.store.files.by_id(banner)?.req()?;
|
||||
let FileSource::PictRs(pictrs_file) = file.source();
|
||||
person.set_image(ctx.pictrs.image_url(pictrs_file.key()));
|
||||
}
|
||||
if !profile.login_required() {
|
||||
person.add_to(public());
|
||||
}
|
||||
|
||||
let any_base = person.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
let mut create = Create::new(person_id, any_base);
|
||||
create
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
|
||||
let any_base = create.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for ProfileUpdated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let profile = ctx.store.profiles.by_id(self.profile_id)?.req()?;
|
||||
let endpoints = ctx.apub.endpoints_for_profile(self.profile_id)?.req()?;
|
||||
let public_key_id = ctx.apub.key_for_profile(profile.id())?.req()?;
|
||||
let public_key_pem = ctx.apub.public_key_for_id(&public_key_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
|
||||
let mut person = ExtendedPerson::new(
|
||||
ApActor::new(endpoints.inbox, Person::new()),
|
||||
PublicKey {
|
||||
public_key: PublicKeyInner {
|
||||
id: public_key_id,
|
||||
owner: person_id.clone(),
|
||||
public_key_pem,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
person
|
||||
.set_id(person_id.clone())
|
||||
.set_following(endpoints.following)
|
||||
.set_followers(endpoints.followers)
|
||||
.set_outbox(endpoints.outbox)
|
||||
.set_endpoints(Endpoints {
|
||||
shared_inbox: Some(endpoints.shared_inbox),
|
||||
..Endpoints::default()
|
||||
})
|
||||
.set_preferred_username(profile.handle())
|
||||
.set_published(profile.published().into());
|
||||
|
||||
if let Some(display_name) = profile.display_name() {
|
||||
person.set_name(display_name);
|
||||
}
|
||||
if let Some(description) = profile.description() {
|
||||
person.set_summary(description);
|
||||
}
|
||||
if let Some(icon) = profile.icon() {
|
||||
let file = ctx.store.files.by_id(icon)?.req()?;
|
||||
let FileSource::PictRs(pictrs_file) = file.source();
|
||||
person.set_icon(ctx.pictrs.image_url(pictrs_file.key()));
|
||||
}
|
||||
if let Some(banner) = profile.banner() {
|
||||
let file = ctx.store.files.by_id(banner)?.req()?;
|
||||
let FileSource::PictRs(pictrs_file) = file.source();
|
||||
person.set_image(ctx.pictrs.image_url(pictrs_file.key()));
|
||||
}
|
||||
if !profile.login_required() {
|
||||
person.add_to(public());
|
||||
}
|
||||
|
||||
let any_base = person.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
let mut update = Update::new(person_id, any_base);
|
||||
update
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
|
||||
let any_base = update.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for ProfileDeleted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let person = ctx.apub.object(&person_id)?.req()?;
|
||||
|
||||
let mut delete = Delete::new(person_id, person);
|
||||
delete
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
|
||||
let any_base = delete.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
65
profiles/src/apub/results/react.rs
Normal file
65
profiles/src/apub/results/react.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use super::{ReactCreated, ReactDeleted};
|
||||
use crate::{Completed, Context, Error, Required};
|
||||
use activitystreams::{
|
||||
activity::{Create, Delete, Like},
|
||||
base::AnyBase,
|
||||
context,
|
||||
prelude::*,
|
||||
public, security,
|
||||
};
|
||||
|
||||
impl Completed for ReactCreated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let react = ctx.store.reacts.by_id(self.react_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(react.profile_id())?.req()?;
|
||||
|
||||
let note_id = if let Some(comment_id) = react.comment_id() {
|
||||
ctx.apub.apub_for_comment(comment_id)?.req()?
|
||||
} else {
|
||||
ctx.apub.apub_for_submission(react.submission_id())?.req()?
|
||||
};
|
||||
|
||||
let published = react.published();
|
||||
let endpoints = ctx.apub.endpoints_for_profile(react.profile_id())?.req()?;
|
||||
|
||||
let mut like = Like::new(person_id.clone(), note_id);
|
||||
like.set_id(ctx.apub.info.gen_id())
|
||||
.set_content(react.react())
|
||||
.set_published(published.into())
|
||||
.set_attributed_to(person_id.clone())
|
||||
.add_to(endpoints.followers)
|
||||
.add_cc(public());
|
||||
|
||||
let like = like.into_any_base()?;
|
||||
ctx.apub.store_object(&like)?;
|
||||
|
||||
let mut create = Create::new(person_id, like);
|
||||
create
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
|
||||
let create = create.into_any_base()?;
|
||||
ctx.apub.store_object(&create)?;
|
||||
|
||||
Ok(create)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for ReactDeleted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let like_id = ctx.apub.apub_for_react(self.react_id)?.req()?;
|
||||
let like = ctx.apub.object(&like_id)?.req()?;
|
||||
|
||||
let mut delete = Delete::new(person_id, like);
|
||||
delete
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let delete = delete.into_any_base()?;
|
||||
ctx.apub.store_object(&delete)?;
|
||||
|
||||
Ok(delete)
|
||||
}
|
||||
}
|
115
profiles/src/apub/results/submission.rs
Normal file
115
profiles/src/apub/results/submission.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
use super::{SubmissionCreated, SubmissionDeleted, SubmissionUpdated};
|
||||
use crate::{
|
||||
store::{FileSource, Submission, Visibility},
|
||||
Completed, Context, Error, Required,
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::{Create, Delete, Update},
|
||||
base::AnyBase,
|
||||
context,
|
||||
object::Note,
|
||||
prelude::*,
|
||||
public, security,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
fn build_submission(
|
||||
submission: Submission,
|
||||
note_id: Url,
|
||||
person_id: Url,
|
||||
ctx: &Context,
|
||||
) -> Result<AnyBase, Error> {
|
||||
let published = submission.published().req()?;
|
||||
|
||||
let endpoints = ctx
|
||||
.apub
|
||||
.endpoints_for_profile(submission.profile_id())?
|
||||
.req()?;
|
||||
|
||||
let mut note = Note::new();
|
||||
note.set_id(note_id)
|
||||
.set_summary(submission.title())
|
||||
.set_published(published.into())
|
||||
.set_attributed_to(person_id);
|
||||
if let Some(description) = submission.description() {
|
||||
note.set_content(description);
|
||||
}
|
||||
match submission.visibility() {
|
||||
Visibility::Public => {
|
||||
note.add_to(public()).add_cc(endpoints.followers);
|
||||
}
|
||||
Visibility::Unlisted => {
|
||||
note.add_to(endpoints.followers).add_cc(public());
|
||||
}
|
||||
Visibility::Followers => {
|
||||
note.add_to(endpoints.followers);
|
||||
}
|
||||
}
|
||||
for file in submission.files() {
|
||||
let file = ctx.store.files.by_id(*file)?.req()?;
|
||||
|
||||
let FileSource::PictRs(pictrs_file) = file.source();
|
||||
|
||||
note.add_attachment(ctx.pictrs.image_url(pictrs_file.key()));
|
||||
}
|
||||
let any_base = note.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
|
||||
impl Completed for SubmissionCreated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let submission = ctx.store.submissions.by_id(self.submission_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(submission.profile_id())?.req()?;
|
||||
let note_id = ctx.apub.info.gen_id();
|
||||
let note = build_submission(submission, note_id, person_id.clone(), ctx)?;
|
||||
|
||||
let mut create = Create::new(person_id, note);
|
||||
create
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = create.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for SubmissionUpdated {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let submission = ctx.store.submissions.by_id(self.submission_id)?.req()?;
|
||||
let person_id = ctx.apub.apub_for_profile(submission.profile_id())?.req()?;
|
||||
let note_id = ctx.apub.apub_for_submission(submission.id())?.req()?;
|
||||
let note = build_submission(submission, person_id.clone(), note_id, ctx)?;
|
||||
|
||||
let mut update = Update::new(person_id, note);
|
||||
update
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = update.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl Completed for SubmissionDeleted {
|
||||
fn to_apub(&self, ctx: &Context) -> Result<AnyBase, Error> {
|
||||
let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?;
|
||||
let note_id = ctx.apub.apub_for_submission(self.submission_id)?.req()?;
|
||||
let note = ctx.apub.object(¬e_id)?.req()?;
|
||||
|
||||
let mut delete = Delete::new(person_id, note);
|
||||
delete
|
||||
.set_id(ctx.apub.info.gen_id())
|
||||
.add_context(context())
|
||||
.add_context(security());
|
||||
let any_base = delete.into_any_base()?;
|
||||
ctx.apub.store_object(&any_base)?;
|
||||
|
||||
Ok(any_base)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use activitystreams::base::AnyBase;
|
||||
use actix_rt::Arbiter;
|
||||
use actix_web::client::Client;
|
||||
use actix_web::{client::Client, dev::Payload, HttpRequest};
|
||||
use sled::Db;
|
||||
use std::{fmt, sync::Arc};
|
||||
use url::Url;
|
||||
|
@ -24,9 +24,10 @@ use apub::ApubIds;
|
|||
use pictrs::ImageInfo;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Context {
|
||||
pub struct Context {
|
||||
store: store::Store,
|
||||
apub: apub::Store,
|
||||
pictrs: Arc<dyn pictrs::ImageInfo + Send + Sync>,
|
||||
arbiter: Arbiter,
|
||||
spawner: Arc<dyn Spawner + Send + Sync>,
|
||||
}
|
||||
|
@ -37,6 +38,7 @@ impl Context {
|
|||
Context {
|
||||
store: state.store.clone(),
|
||||
apub: state.apub.clone(),
|
||||
pictrs: state.pictrs.info.clone(),
|
||||
arbiter: Arbiter::current(),
|
||||
spawner: state.spawner.clone(),
|
||||
}
|
||||
|
@ -51,6 +53,12 @@ pub enum Error {
|
|||
#[error("ActivityPub object is malformed")]
|
||||
Invalid,
|
||||
|
||||
#[error("Error deleting file: {0}")]
|
||||
DeleteFile(#[from] pictrs::DeleteError),
|
||||
|
||||
#[error("Error uploading file: {0}")]
|
||||
UploadFile(#[from] pictrs::UploadError),
|
||||
|
||||
#[error("Failed to serialize or deseiralize data: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
|
@ -66,6 +74,7 @@ pub trait Spawner {
|
|||
fn download_images(&self, images: Vec<Url>, stack: Vec<AnyBase>);
|
||||
fn purge_file(&self, file_id: Uuid);
|
||||
fn process(&self, any_base: AnyBase, stack: Vec<AnyBase>);
|
||||
fn deliver(&self, completed: &dyn Completed);
|
||||
}
|
||||
|
||||
enum RecoverableError {
|
||||
|
@ -77,6 +86,10 @@ trait Action {
|
|||
fn perform(&self, context: &Context) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub trait Completed {
|
||||
fn to_apub(&self, context: &Context) -> Result<AnyBase, Error>;
|
||||
}
|
||||
|
||||
trait Required<T> {
|
||||
fn req(self) -> Result<T, Error>;
|
||||
}
|
||||
|
@ -99,7 +112,7 @@ pub struct State {
|
|||
impl State {
|
||||
pub fn build(
|
||||
pictrs_upstream: Url,
|
||||
image_info: impl ImageInfo + 'static,
|
||||
image_info: impl ImageInfo + Send + Sync + 'static,
|
||||
apub_info: impl ApubIds + Send + Sync + 'static,
|
||||
spawner: impl Spawner + Send + Sync + 'static,
|
||||
client: Client,
|
||||
|
@ -120,6 +133,55 @@ impl State {
|
|||
apub::ingest(any_base, &context, stack)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_image(&self, url: &Url) -> Result<Uuid, Error> {
|
||||
let images = self.pictrs.download_image(url).await?;
|
||||
|
||||
// safe because we already checked emptiness
|
||||
let image = images.images().next().unwrap();
|
||||
|
||||
let file_source = store::FileSource::PictRs(store::PictRsFile::new(
|
||||
image.key(),
|
||||
image.token(),
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.mime(),
|
||||
));
|
||||
let file = self.store.files.create(&file_source)?;
|
||||
|
||||
Ok(file.id())
|
||||
}
|
||||
|
||||
pub async fn upload_image(&self, req: HttpRequest, body: Payload) -> Result<Vec<Uuid>, Error> {
|
||||
let images = self.pictrs.proxy_upload(req, body).await?;
|
||||
|
||||
let mut files = vec![];
|
||||
for image in images.images() {
|
||||
let file_source = store::FileSource::PictRs(store::PictRsFile::new(
|
||||
image.key(),
|
||||
image.token(),
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.mime(),
|
||||
));
|
||||
let file = self.store.files.create(&file_source)?;
|
||||
files.push(file.id());
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub async fn delete_file(&self, file_id: Uuid) -> Result<(), Error> {
|
||||
let file = self.store.files.by_id(file_id)?.req()?;
|
||||
|
||||
let store::FileSource::PictRs(image) = file.source();
|
||||
|
||||
self.pictrs.delete_image(image.key(), image.token()).await?;
|
||||
|
||||
self.store.files.delete(file_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for State {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use actix_web::{body::BodyStream, client::Client, dev::Payload, HttpRequest, HttpResponse};
|
||||
use std::{fmt, rc::Rc};
|
||||
use std::{fmt, sync::Arc};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -7,12 +7,6 @@ struct Serde<T> {
|
|||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> Serde<T> {
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
Serde { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> serde::Serialize for Serde<T>
|
||||
where
|
||||
T: std::fmt::Display,
|
||||
|
@ -66,6 +60,18 @@ impl Image {
|
|||
pub(crate) fn token(&self) -> &str {
|
||||
&self.delete_token
|
||||
}
|
||||
|
||||
pub(crate) fn width(&self) -> usize {
|
||||
self.details.width
|
||||
}
|
||||
|
||||
pub(crate) fn height(&self) -> usize {
|
||||
self.details.height
|
||||
}
|
||||
|
||||
pub(crate) fn mime(&self) -> mime::Mime {
|
||||
self.details.content_type.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
@ -75,14 +81,10 @@ pub struct Images {
|
|||
}
|
||||
|
||||
impl Images {
|
||||
pub(crate) fn is_err(&self) -> bool {
|
||||
fn is_err(&self) -> bool {
|
||||
self.files.is_none()
|
||||
}
|
||||
|
||||
pub(crate) fn message(&self) -> &str {
|
||||
&self.msg
|
||||
}
|
||||
|
||||
pub(crate) fn images(&self) -> impl DoubleEndedIterator<Item = &Image> {
|
||||
self.files.iter().flat_map(|v| v.iter())
|
||||
}
|
||||
|
@ -95,16 +97,21 @@ pub enum UploadError {
|
|||
|
||||
#[error("Failed to parse image json")]
|
||||
Json,
|
||||
|
||||
#[error("Error in pict-rs: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DeleteError {
|
||||
#[error("Failed to send request")]
|
||||
Request,
|
||||
|
||||
#[error("Status was not 2XX")]
|
||||
Status,
|
||||
}
|
||||
|
||||
pub trait ImageInfo {
|
||||
fn icon_sizes(&self) -> &[u64];
|
||||
fn banner_sizes(&self) -> &[u64];
|
||||
fn thumbnail_sizes(&self) -> &[u64];
|
||||
|
||||
fn icon_url(&self, key: &str, kind: ImageType, size: u64) -> Url;
|
||||
fn banner_url(&self, key: &str, kind: ImageType, size: u64) -> Url;
|
||||
fn thumbnail_url(&self, key: &str, kind: ImageType, size: u64) -> Url;
|
||||
fn image_url(&self, key: &str) -> Url;
|
||||
}
|
||||
|
||||
|
@ -123,24 +130,24 @@ pub enum ImageType {
|
|||
#[derive(Clone)]
|
||||
pub struct State {
|
||||
upstream: Url,
|
||||
image_info: Rc<dyn ImageInfo>,
|
||||
pub(super) info: Arc<dyn ImageInfo + Send + Sync>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) fn new(upstream: Url, image_info: impl ImageInfo + 'static, client: Client) -> Self {
|
||||
pub(super) fn new(
|
||||
upstream: Url,
|
||||
image_info: impl ImageInfo + Send + Sync + 'static,
|
||||
client: Client,
|
||||
) -> Self {
|
||||
State {
|
||||
upstream,
|
||||
image_info: Rc::new(image_info),
|
||||
info: Arc::new(image_info),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn image_info(&self) -> &dyn ImageInfo {
|
||||
&*self.image_info
|
||||
}
|
||||
|
||||
pub(crate) async fn serve_thumbnail(
|
||||
pub async fn serve_thumbnail(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
key: &str,
|
||||
|
@ -150,7 +157,7 @@ impl State {
|
|||
self.serve_image(self.thumbnail(key, kind, size), req).await
|
||||
}
|
||||
|
||||
pub(crate) async fn serve_banner(
|
||||
pub async fn serve_banner(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
key: &str,
|
||||
|
@ -160,7 +167,7 @@ impl State {
|
|||
self.serve_image(self.banner(key, kind, size), req).await
|
||||
}
|
||||
|
||||
pub(crate) async fn serve_icon(
|
||||
pub async fn serve_icon(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
key: &str,
|
||||
|
@ -170,7 +177,7 @@ impl State {
|
|||
self.serve_image(self.icon(key, kind, size), req).await
|
||||
}
|
||||
|
||||
pub(crate) async fn serve_full(
|
||||
pub async fn serve_full(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
key: &str,
|
||||
|
@ -178,29 +185,6 @@ impl State {
|
|||
self.serve_image(self.full_size(key), req).await
|
||||
}
|
||||
|
||||
pub(crate) async fn proxy_upload(
|
||||
&self,
|
||||
req: HttpRequest,
|
||||
body: Payload,
|
||||
) -> Result<Images, UploadError> {
|
||||
let client_req = self.client.request_from(self.upload().as_str(), req.head());
|
||||
|
||||
let client_req = if let Some(addr) = req.head().peer_addr {
|
||||
client_req.header("X-Forwarded-For", addr.to_string())
|
||||
} else {
|
||||
client_req
|
||||
};
|
||||
|
||||
let mut res = client_req
|
||||
.send_stream(body)
|
||||
.await
|
||||
.map_err(|_| UploadError::Request)?;
|
||||
|
||||
let images = res.json::<Images>().await.map_err(|_| UploadError::Json)?;
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
async fn serve_image(
|
||||
&self,
|
||||
url: Url,
|
||||
|
@ -224,13 +208,81 @@ impl State {
|
|||
Ok(client_res.body(BodyStream::new(res)))
|
||||
}
|
||||
|
||||
pub(crate) fn upload(&self) -> Url {
|
||||
pub(super) async fn delete_image(&self, key: &str, token: &str) -> Result<(), DeleteError> {
|
||||
let res = self
|
||||
.client
|
||||
.delete(self.delete(key, token).as_str())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{}", e);
|
||||
DeleteError::Request
|
||||
})?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
return Err(DeleteError::Status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn proxy_upload(
|
||||
&self,
|
||||
req: HttpRequest,
|
||||
body: Payload,
|
||||
) -> Result<Images, UploadError> {
|
||||
let client_req = self.client.request_from(self.upload().as_str(), req.head());
|
||||
|
||||
let client_req = if let Some(addr) = req.head().peer_addr {
|
||||
client_req.header("X-Forwarded-For", addr.to_string())
|
||||
} else {
|
||||
client_req
|
||||
};
|
||||
|
||||
let mut res = client_req
|
||||
.send_stream(body)
|
||||
.await
|
||||
.map_err(|_| UploadError::Request)?;
|
||||
|
||||
let images = res.json::<Images>().await.map_err(|_| UploadError::Json)?;
|
||||
|
||||
if images.is_err() {
|
||||
return Err(UploadError::Other(images.msg));
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
pub(super) async fn download_image(&self, url: &Url) -> Result<Images, UploadError> {
|
||||
let mut res = self
|
||||
.client
|
||||
.get(self.download(url).as_str())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("Error in download: {}", e);
|
||||
UploadError::Request
|
||||
})?;
|
||||
|
||||
let images: Images = res.json().await.map_err(|e| {
|
||||
log::error!("Error in download: {}", e);
|
||||
UploadError::Json
|
||||
})?;
|
||||
|
||||
if images.is_err() {
|
||||
return Err(UploadError::Other(images.msg));
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
fn upload(&self) -> Url {
|
||||
let mut url = self.upstream.clone();
|
||||
url.set_path("/image");
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn full_size(&self, key: &str) -> Url {
|
||||
fn full_size(&self, key: &str) -> Url {
|
||||
let mut url = self.upstream.clone();
|
||||
url.set_path(&format!("/image/original/{}", key));
|
||||
url
|
||||
|
@ -242,25 +294,32 @@ impl State {
|
|||
url
|
||||
}
|
||||
|
||||
pub(crate) fn icon(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
fn icon(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
let mut url = self.process(kind);
|
||||
url.set_query(Some(&format!("src={}&crop=1x1&resize={}", key, size)));
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn banner(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
fn banner(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
let mut url = self.process(kind);
|
||||
url.set_query(Some(&format!("src={}&crop=3x1&resize={}", key, size)));
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn thumbnail(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
fn thumbnail(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
||||
let mut url = self.process(kind);
|
||||
url.set_query(Some(&format!("src={}&resize={}", key, size)));
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn delete(&self, key: &str, token: &str) -> Url {
|
||||
fn download(&self, image_url: &Url) -> Url {
|
||||
let mut url = self.upstream.clone();
|
||||
url.set_path("/image/download");
|
||||
url.set_query(Some(&format!("url={}", image_url)));
|
||||
url
|
||||
}
|
||||
|
||||
fn delete(&self, key: &str, token: &str) -> Url {
|
||||
let mut url = self.upstream.clone();
|
||||
url.set_path(&format!("/image/delete/{}/{}", token, key));
|
||||
url
|
||||
|
|
|
@ -5,7 +5,6 @@ use uuid::Uuid;
|
|||
|
||||
mod comment;
|
||||
mod file;
|
||||
mod owner;
|
||||
mod profile;
|
||||
mod react;
|
||||
mod submission;
|
||||
|
@ -13,7 +12,6 @@ pub mod view;
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
pub owners: owner::Store,
|
||||
pub profiles: profile::Store,
|
||||
pub files: file::Store,
|
||||
pub submissions: submission::Store,
|
||||
|
@ -25,7 +23,6 @@ pub struct Store {
|
|||
impl Store {
|
||||
pub fn build(db: &Db) -> Result<Store, sled::Error> {
|
||||
Ok(Store {
|
||||
owners: owner::Store::build(db)?,
|
||||
profiles: profile::Store::build(db)?,
|
||||
files: file::Store::build(db)?,
|
||||
submissions: submission::Store::build(db)?,
|
||||
|
@ -48,22 +45,6 @@ impl OwnerSource {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Owner {
|
||||
id: Uuid,
|
||||
source: OwnerSource,
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
pub(crate) fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(crate) fn source(&self) -> &OwnerSource {
|
||||
&self.source
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Profile {
|
||||
id: Uuid,
|
||||
|
@ -104,10 +85,6 @@ impl Profile {
|
|||
&self.handle
|
||||
}
|
||||
|
||||
pub(crate) fn domain(&self) -> &str {
|
||||
&self.domain
|
||||
}
|
||||
|
||||
pub(crate) fn display_name(&self) -> Option<&str> {
|
||||
self.display_name.as_ref().map(|dn| dn.as_str())
|
||||
}
|
||||
|
@ -143,12 +120,28 @@ pub struct PictRsFile {
|
|||
}
|
||||
|
||||
impl PictRsFile {
|
||||
pub(crate) fn new(
|
||||
key: &str,
|
||||
token: &str,
|
||||
width: usize,
|
||||
height: usize,
|
||||
media_type: mime::Mime,
|
||||
) -> Self {
|
||||
PictRsFile {
|
||||
key: key.to_owned(),
|
||||
token: token.to_owned(),
|
||||
width,
|
||||
height,
|
||||
media_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key(&self) -> &str {
|
||||
&self.key
|
||||
}
|
||||
|
||||
pub(crate) fn mime(&self) -> mime::Mime {
|
||||
self.media_type.clone()
|
||||
pub(crate) fn token(&self) -> &str {
|
||||
&self.token
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,6 +157,10 @@ pub struct File {
|
|||
}
|
||||
|
||||
impl File {
|
||||
pub(crate) fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(crate) fn source(&self) -> &FileSource {
|
||||
&self.source
|
||||
}
|
||||
|
@ -576,7 +573,6 @@ impl fmt::Display for OwnerSource {
|
|||
impl fmt::Debug for Store {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Store")
|
||||
.field("owners", &"OwnerStore")
|
||||
.field("profiles", &"ProfileStore")
|
||||
.field("files", &"FileStore")
|
||||
.field("submissions", &"SubmissionStore")
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
use super::{Owner, OwnerSource, StoreError, Undo};
|
||||
use chrono::{DateTime, Utc};
|
||||
use sled::{Db, Transactional, Tree};
|
||||
use std::io::Cursor;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Store {
|
||||
owner_tree: Tree,
|
||||
source_tree: Tree,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
enum StoredOwnerSource<'a> {
|
||||
Local(Uuid),
|
||||
Remote(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct StoredOwner<'a> {
|
||||
id: Uuid,
|
||||
#[serde(borrow)]
|
||||
source: StoredOwnerSource<'a>,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn build(db: &Db) -> Result<Self, sled::Error> {
|
||||
Ok(Store {
|
||||
owner_tree: db.open_tree("profiles/owners")?,
|
||||
source_tree: db.open_tree("profiles/owners/source")?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create(&self, source: &OwnerSource) -> Result<Owner, StoreError> {
|
||||
let mut id;
|
||||
let mut stored_owner;
|
||||
|
||||
let now = Utc::now().into();
|
||||
let mut stored_owner_vec = vec![];
|
||||
|
||||
let stored_source = match source {
|
||||
OwnerSource::Local(id) => StoredOwnerSource::Local(*id),
|
||||
OwnerSource::Remote(s) => StoredOwnerSource::Remote(&s),
|
||||
};
|
||||
|
||||
while {
|
||||
stored_owner_vec.clear();
|
||||
let writer = Cursor::new(&mut stored_owner_vec);
|
||||
id = Uuid::new_v4();
|
||||
stored_owner = StoredOwner {
|
||||
id,
|
||||
source: stored_source.clone(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
serde_json::to_writer(writer, &stored_owner)?;
|
||||
self.owner_tree
|
||||
.compare_and_swap(
|
||||
id_owner_key(id),
|
||||
None as Option<&[u8]>,
|
||||
Some(stored_owner_vec.as_slice()),
|
||||
)?
|
||||
.is_err()
|
||||
} {}
|
||||
|
||||
let source_key = source_key(stored_source.clone());
|
||||
|
||||
if let Err(e) = self
|
||||
.source_tree
|
||||
.insert(source_key, id.to_string().as_bytes())
|
||||
{
|
||||
self.owner_tree.remove(id_owner_key(id))?;
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
Ok(stored_owner.into())
|
||||
}
|
||||
|
||||
pub fn by_source(&self, source: &OwnerSource) -> Result<Option<Owner>, StoreError> {
|
||||
let stored_source = match source {
|
||||
OwnerSource::Local(id) => StoredOwnerSource::Local(*id),
|
||||
OwnerSource::Remote(s) => StoredOwnerSource::Remote(&s),
|
||||
};
|
||||
|
||||
let source_key = source_key(stored_source);
|
||||
|
||||
let id_ivec = match self.source_tree.get(source_key)? {
|
||||
Some(ivec) => ivec,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let id_str = String::from_utf8_lossy(&id_ivec);
|
||||
let id: Uuid = id_str.parse().expect("Uuid is valid");
|
||||
|
||||
let owner_ivec = match self.owner_tree.get(id_owner_key(id))? {
|
||||
Some(ivec) => ivec,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let stored_owner: StoredOwner = serde_json::from_slice(&owner_ivec)?;
|
||||
|
||||
Ok(Some(stored_owner.into()))
|
||||
}
|
||||
|
||||
pub fn delete(&self, owner_id: Uuid) -> Result<Option<Undo<Owner>>, StoreError> {
|
||||
let owner_ivec = match self.owner_tree.get(id_owner_key(owner_id))? {
|
||||
Some(ivec) => ivec,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let stored_owner: StoredOwner = serde_json::from_slice(&owner_ivec)?;
|
||||
|
||||
let id = owner_id;
|
||||
let source_key = source_key(stored_owner.source.clone());
|
||||
|
||||
let owner = stored_owner.into();
|
||||
|
||||
&[&self.owner_tree, &self.source_tree].transaction(move |trees| {
|
||||
let owner_tree = &trees[0];
|
||||
let source_tree = &trees[1];
|
||||
|
||||
source_tree.remove(source_key.as_bytes())?;
|
||||
owner_tree.remove(id_owner_key(id).as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(Some(Undo(owner)))
|
||||
}
|
||||
}
|
||||
|
||||
fn id_owner_key(id: Uuid) -> String {
|
||||
format!("/owner/{}/data", id)
|
||||
}
|
||||
|
||||
fn source_key<'a>(source: StoredOwnerSource<'a>) -> String {
|
||||
match source {
|
||||
StoredOwnerSource::Local(id) => format!("/source/local:{}", id),
|
||||
StoredOwnerSource::Remote(s) => format!("/source/remote:{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<StoredOwner<'a>> for Owner {
|
||||
fn from(so: StoredOwner<'a>) -> Self {
|
||||
Owner {
|
||||
id: so.id,
|
||||
source: match so.source {
|
||||
StoredOwnerSource::Local(id) => OwnerSource::Local(id),
|
||||
StoredOwnerSource::Remote(s) => OwnerSource::Remote(s.to_owned()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue