diff --git a/blueprints/api.py b/blueprints/api.py index 45cc598..4b711d1 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -493,11 +493,13 @@ def api_new_question() -> _Response: ) } of = _user_api_arg("of") + print(of) if of == "anyOf": choices["anyOf"] = answers else: choices["oneOf"] = answers + print(choices) raw_question = dict( attributedTo=MY_PERSON.id, cc=list(set(cc)), diff --git a/blueprints/tasks.py b/blueprints/tasks.py index ab2d160..f0ae6a6 100644 --- a/blueprints/tasks.py +++ b/blueprints/tasks.py @@ -20,7 +20,6 @@ from core.activitypub import SIG_AUTH from core.activitypub import Box from core.activitypub import _actor_hash from core.activitypub import _add_answers_to_question -from core.activitypub import no_cache from core.activitypub import post_to_outbox from core.activitypub import update_cached_actor from core.db import update_one_activity @@ -142,8 +141,7 @@ def task_cache_object() -> _Response: obj = activity.get_object() # Refetch the object actor (without cache) - with no_cache(): - obj_actor = ap.fetch_remote_activity(obj.get_actor().id) + obj_actor = ap.fetch_remote_activity(obj.get_actor().id, no_cache=True) cache = {MetaKey.OBJECT: obj.to_dict(embed=True)} @@ -269,8 +267,7 @@ def task_cache_actor() -> _Response: app.logger.info(f"activity={activity!r}") # Reload the actor without caching (in case it got upated) - with no_cache(): - actor = ap.fetch_remote_activity(activity.get_actor().id) + actor = ap.fetch_remote_activity(activity.get_actor().id, no_cache=True) # Fetch the Open Grah metadata if it's a `Create` if activity.has_type(ap.ActivityType.CREATE): diff --git a/core/activitypub.py b/core/activitypub.py index 8a5367d..221dc69 100644 --- a/core/activitypub.py +++ b/core/activitypub.py @@ -2,12 +2,11 @@ import binascii import hashlib import logging import os -from contextlib import contextmanager +import time from datetime import datetime from datetime import timezone from typing import Any from typing import Dict -from typing import Iterator from typing import List from typing import Optional from urllib.parse import urljoin @@ -45,22 +44,10 @@ _NewMeta = Dict[str, Any] SIG_AUTH = HTTPSigAuth(KEY) -_ACTIVITY_CACHE_ENABLED = True ACTORS_CACHE = LRUCache(maxsize=256) MY_PERSON = ap.Person(**ME) -@contextmanager -def no_cache() -> Iterator[None]: - """Context manager for disabling the "DB cache" when fetching AP activities.""" - global _ACTIVITY_CACHE_ENABLED - _ACTIVITY_CACHE_ENABLED = False - try: - yield - finally: - _ACTIVITY_CACHE_ENABLED = True - - def _remove_id(doc: ap.ObjectType) -> ap.ObjectType: """Helper for removing MongoDB's `_id` field.""" doc = doc.copy() @@ -177,6 +164,7 @@ def post_to_inbox(activity: ap.BaseActivity) -> None: return save(Box.INBOX, activity) + time.sleep(1) logger.info(f"spawning tasks for {activity!r}") if not activity.has_type([ap.ActivityType.DELETE, ap.ActivityType.UPDATE]): Tasks.cache_actor(activity.id) @@ -202,6 +190,7 @@ def post_to_outbox(activity: ap.BaseActivity) -> str: activity.reset_object_cache() save(Box.OUTBOX, activity) + time.sleep(5) Tasks.cache_actor(activity.id) Tasks.finish_post_to_outbox(activity.id) return activity.id @@ -361,8 +350,8 @@ class MicroblogPubBackend(Backend): logger.info(f"dereference {iri} via HTTP") return super().fetch_iri(iri) - def fetch_iri(self, iri: str, no_cache=False) -> ap.ObjectType: - if not no_cache and _ACTIVITY_CACHE_ENABLED: + def fetch_iri(self, iri: str, **kwargs: Any) -> ap.ObjectType: + if not kwargs.pop("no_cache", False): # Fetch the activity by checking the local DB first data = self._fetch_iri(iri) logger.debug(f"_fetch_iri({iri!r}) == {data!r}") @@ -397,21 +386,26 @@ class MicroblogPubBackend(Backend): logger.info("invalid choice") return + # Hash the choice/answer (so we can use it as a key) + answer_key = _answer_key(choice) + + is_single_choice = bool(question._data.get("oneOf", [])) + dup_query = { + "activity.object.actor": create.get_actor().id, + "meta.answer_to": question.id, + **({} if is_single_choice else {"meta.poll_answer_choice": choice}), + } + + print(f"dup_q={dup_query}") # Check for duplicate votes - if DB.activities.find_one( - { - "activity.object.actor": create.get_actor().id, - "meta.answer_to": question.id, - } - ): + if DB.activities.find_one(dup_query): logger.info("duplicate response") return # Update the DB - answer_key = _answer_key(choice) DB.activities.update_one( - {"activity.object.id": question.id}, + {"meta.object_id": question.id}, { "$inc": { "meta.question_replies": 1, @@ -425,6 +419,7 @@ class MicroblogPubBackend(Backend): { "$set": { "meta.answer_to": question.id, + "meta.poll_answer_choice": choice, "meta.stream": False, "meta.poll_answer": True, } @@ -462,7 +457,11 @@ class MicroblogPubBackend(Backend): # Keep track of our own votes DB.activities.update_one( {"activity.object.id": reply.id, "box": "inbox"}, - {"$set": {"meta.voted_for": create.get_object().name}}, + { + "$set": { + f"meta.poll_answers_sent.{_answer_key(create.get_object().name)}": True + } + }, ) return None diff --git a/core/inbox.py b/core/inbox.py index 1d948c7..850a807 100644 --- a/core/inbox.py +++ b/core/inbox.py @@ -8,7 +8,6 @@ from little_boxes.errors import NotAnActivityError import config from core.activitypub import _answer_key -from core.activitypub import no_cache from core.activitypub import post_to_outbox from core.activitypub import update_cached_actor from core.db import DB @@ -93,8 +92,7 @@ def _update_process_inbox(update: ap.Update, new_meta: _NewMeta) -> None: ) elif obj.has_type(ap.ACTOR_TYPES): - with no_cache(): - actor = ap.fetch_remote_activity(obj.get_actor().id) + actor = ap.fetch_remote_activity(obj.get_actor().id, no_cache=True) update_cached_actor(actor) else: diff --git a/templates/new.html b/templates/new.html index 0fa98fc..ce85c02 100644 --- a/templates/new.html +++ b/templates/new.html @@ -49,12 +49,10 @@

- - +

{% for i in range(4) %}

diff --git a/templates/utils.html b/templates/utils.html index b8c106b..0fccbf6 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -88,7 +88,7 @@ {% set pct = cnt * 100.0 / total_votes %} {% endif %}
  • - {% if session.logged_in and not meta.voted_for and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %} + {% if session.logged_in and not meta.poll_answers_sent and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %}
    @@ -100,10 +100,40 @@ {{ '%0.0f'| format(pct) }}% - {{ oneOf.name }} {% if oneOf.name == meta.voted_for %}(your vote){% endif %} + {{ oneOf.name }} {% if oneOf.name | poll_answer_key in meta.poll_answers_sent %}(your vote){% endif %}
  • {% endfor %} + {% if obj.anyOf %} + + {% for anyOf in obj.anyOf %} + {% set pct = 0 %} + {% if total_votes > 0 %} + {% set cnt = anyOf.name | get_answer_count(obj, meta) %} + {% set pct = cnt * 100.0 / total_votes %} + {% endif %} +
  • + {% set already_voted = anyOf.name | poll_answer_key in meta.poll_answers_sent %} + {% if session.logged_in and not already_voted and not (real_end_time | gtnow) and not (obj.id | is_from_outbox) %} + + + + + + + + {% elif session.logged_in and already_voted %} + + {% endif %} + + + {{ '%0.0f'| format(pct) }}% + {{ anyOf.name }} {% if anyOf.name | poll_answer_key in meta.poll_answers_sent %}(your vote){% endif %} + +
  • + {% endfor %} + + {% endif %}

    {% if real_end_time | gtnow %} diff --git a/utils/template_filters.py b/utils/template_filters.py index 70403df..9cb9757 100644 --- a/utils/template_filters.py +++ b/utils/template_filters.py @@ -206,6 +206,11 @@ def get_actor(url): return f"Error<{url}/{exc!r}>" +@filters.app_template_filter() +def poll_answer_key(choice: str) -> str: + return _answer_key(choice) + + @filters.app_template_filter() def get_answer_count(choice, obj, meta): count_from_meta = meta.get("question_answers", {}).get(_answer_key(choice), 0)