diff --git a/activitypub.py b/activitypub.py index 1b51789..e00a18f 100644 --- a/activitypub.py +++ b/activitypub.py @@ -81,9 +81,8 @@ def _get_actor_id(actor: ObjectOrIDType) -> str: class BaseActivity(object): - ACTIVITY_TYPE = None # type: Optional[ActivityTypes] - NO_CONTEXT = False - ALLOWED_OBJECT_TYPES = None # type: List[ActivityTypes] + ACTIVITY_TYPE: Optional[ActivityTypes] = None + ALLOWED_OBJECT_TYPES: List[ActivityTypes] = [] def __init__(self, **kwargs) -> None: if not self.ACTIVITY_TYPE: @@ -92,7 +91,7 @@ class BaseActivity(object): if kwargs.get('type') is not None and kwargs.pop('type') != self.ACTIVITY_TYPE.value: raise ValueError('Expect the type to be {}'.format(self.ACTIVITY_TYPE)) - self._data = {'type': self.ACTIVITY_TYPE.value} # type: Dict[str, Any] + self._data: Dict[str, Any] = {'type': self.ACTIVITY_TYPE.value} if 'id' in kwargs: self._data['id'] = kwargs.pop('id') @@ -230,7 +229,7 @@ class BaseActivity(object): p = parse_activity(obj) - self.__obj = p # type: BaseActivity + self.__obj: BaseActivity = p return p def _to_dict(self, data: ObjectType) -> ObjectType: @@ -267,6 +266,9 @@ class BaseActivity(object): def _undo_inbox(self) -> None: raise NotImplementedError + def _should_purge_cache(self) -> bool: + raise NotImplementedError + def process_from_inbox(self) -> None: self.verify() actor = self.get_actor() @@ -332,7 +334,7 @@ class BaseActivity(object): def recipients(self) -> List[str]: recipients = self._recipients() - out = [] # type: List[str] + out: List[str] = [] for recipient in recipients: if recipient in PUBLIC_INSTANCES: if recipient not in out: @@ -455,6 +457,10 @@ class Follow(BaseActivity): def build_undo(self) -> BaseActivity: return Undo(object=self.to_dict(embed=True)) + def _should_purge_cache(self) -> bool: + # Receiving a follow activity in the inbox should reset the application cache + return True + class Accept(BaseActivity): ACTIVITY_TYPE = ActivityTypes.ACCEPT @@ -468,6 +474,12 @@ class Accept(BaseActivity): if DB.following.find({'remote_actor': remote_actor}).count() == 0: DB.following.insert_one({'remote_actor': remote_actor}) + def _should_purge_cache(self) -> bool: + # Receiving an accept activity in the inbox should reset the application cache + # (a follow request has been accepted) + return True + + class Undo(BaseActivity): ACTIVITY_TYPE = ActivityTypes.UNDO @@ -628,7 +640,7 @@ class Update(BaseActivity): obj = self.get_object() update_prefix = 'activity.object.' - update = {'$set': dict(), '$unset': dict()} # type: Dict[str, Any] + update: Dict[str, Any] = {'$set': dict(), '$unset': dict()} update['$set'][f'{update_prefix}updated'] = datetime.utcnow().replace(microsecond=0).isoformat() + 'Z' for k, v in obj._data.items(): if k in ['id', 'type']: @@ -734,7 +746,7 @@ class Note(BaseActivity): def _recipients(self) -> List[str]: # TODO(tsileo): audience support? - recipients = [] # type: List[str] + recipients: List[str] = [] # If the note is public, we publish it to the defined "public instances" if AS_PUBLIC in self._data.get('to', []): @@ -847,7 +859,7 @@ def build_inbox_json_feed(path: str, request_cursor: Optional[str] = None) -> Di data = [] cursor = None - q = {'type': 'Create'} # type: Dict[str, Any] + q: Dict[str, Any] = {'type': 'Create'} if request_cursor: q['_id'] = {'$lt': request_cursor} @@ -889,7 +901,7 @@ def parse_collection(payload: Optional[Dict[str, Any]] = None, url: Optional[str return [doc['remote_actor'] for doc in DB.following.find()] # Go through all the pages - out = [] # type: List[str] + out: List[str] = [] if url: resp = requests.get(url, headers={'Accept': 'application/activity+json'}) resp.raise_for_status() diff --git a/app.py b/app.py index 7763b01..e521e2a 100644 --- a/app.py +++ b/app.py @@ -46,6 +46,8 @@ from config import ACTOR_SERVICE from config import OBJECT_SERVICE from config import PASS from config import HEADERS +from config import VERSION +from config import custom_cache_purge_hook from utils.httpsig import HTTPSigAuth, verify_request from utils.key import get_secret_key from utils.webfinger import get_remote_follow_template @@ -69,7 +71,11 @@ def verify_pass(pwd): @app.context_processor def inject_config(): - return dict(config=config, logged_in=session.get('logged_in', False)) + return dict( + microblogpub_version=VERSION, + config=config, + logged_in=session.get('logged_in', False), + ) @app.after_request def set_x_powered_by(response): @@ -453,6 +459,9 @@ def outbox(): activity.post_to_outbox() + # Purge the cache if a custom hook is set, as new content was published + custom_cache_purge_hook() + return Response(status=201, headers={'Location': activity.id}) diff --git a/config.py b/config.py index b8ff84f..250916c 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,4 @@ +import subprocess import os import yaml from pymongo import MongoClient @@ -7,8 +8,17 @@ from utils.key import Key from utils.actor_service import ActorService from utils.object_service import ObjectService +def noop(): + pass -VERSION = '1.0.0' + +CUSTOM_CACHE_HOOKS = False +try: + from cache_hooks import purge as custom_cache_purge_hook +except ModuleNotFoundError: + custom_cache_purge_hook = noop + +VERSION = subprocess.check_output(['git', 'describe', '--always']).split()[0].decode('utf-8') CTX_AS = 'https://www.w3.org/ns/activitystreams' CTX_SECURITY = 'https://w3id.org/security/v1' diff --git a/templates/layout.html b/templates/layout.html index a50d6fb..25f3880 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -22,7 +22,7 @@