Skip to content

Commit f9c206d

Browse files
authored
add integration to social (#87)
* add integration to social * update social URL
1 parent 52c64ea commit f9c206d

2 files changed

Lines changed: 138 additions & 1 deletion

File tree

website/apps.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ def ready(self):
1111
last_active_signal_from_answer,
1212
last_active_signal_from_reply,
1313
home_cache_invalidator,
14+
webhook_on_create,
15+
webhook_on_delete,
1416
)
1517

1618
post_save.connect(
@@ -53,4 +55,26 @@ def ready(self):
5355
home_cache_invalidator,
5456
sender=AnswerComment,
5557
dispatch_uid='home_cache_invalidator_answercomment_delete',
58+
)
59+
60+
# Webhook signals for social
61+
post_save.connect(
62+
webhook_on_create,
63+
sender=Question,
64+
dispatch_uid='webhook_question_created',
65+
)
66+
post_save.connect(
67+
webhook_on_create,
68+
sender=Answer,
69+
dispatch_uid='webhook_answer_created',
70+
)
71+
post_delete.connect(
72+
webhook_on_delete,
73+
sender=Question,
74+
dispatch_uid='webhook_question_deleted',
75+
)
76+
post_delete.connect(
77+
webhook_on_delete,
78+
sender=Answer,
79+
dispatch_uid='webhook_answer_deleted',
5680
)

website/signals.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import json
2+
import logging
3+
import threading
4+
5+
import requests
6+
from django.conf import settings
17
from django.utils import timezone
28
from django.core.cache import cache
39

10+
logger = logging.getLogger(__name__)
11+
412

513
HOME_CACHE_KEYS = [
614
'home:categories',
@@ -35,4 +43,109 @@ def last_active_signal_from_reply(sender, instance, created, **kwargs):
3543

3644

3745
def home_cache_invalidator(sender, instance, **kwargs):
38-
clear_home_cache()
46+
clear_home_cache()
47+
48+
49+
# webhook helpers for social
50+
51+
def _send_webhook(payload):
52+
"""Fire-and-forget POST to the Social platform's webhook endpoint.
53+
54+
Runs in a daemon thread so the forum response is never delayed.
55+
Fails silently — the forum must keep working even if the Social app is down.
56+
"""
57+
58+
webhook_url = "https://social.edupyramids.org/api/webhooks/forum"
59+
60+
if not webhook_url:
61+
return
62+
63+
headers = {
64+
'Content-Type': 'application/json',
65+
}
66+
67+
def _post():
68+
try:
69+
resp = requests.post(
70+
webhook_url,
71+
data=json.dumps(payload),
72+
headers=headers,
73+
timeout=10,
74+
)
75+
logger.info(
76+
'Webhook sent: %s → %s (status %s)',
77+
payload.get('event'), webhook_url, resp.status_code,
78+
)
79+
except requests.RequestException as exc:
80+
logger.warning('Webhook delivery failed: %s', exc)
81+
82+
thread = threading.Thread(target=_post, daemon=True)
83+
thread.start()
84+
85+
86+
def _build_payload(event_type, instance, sender):
87+
"""Build a consistent webhook payload for both Question and Answer events."""
88+
from .models import Question, Answer
89+
from django.contrib.auth import get_user_model
90+
91+
User = get_user_model()
92+
93+
94+
user_email = ""
95+
try:
96+
user = User.objects.get(id=instance.uid)
97+
user_email = user.email
98+
except User.DoesNotExist:
99+
pass
100+
101+
payload = {
102+
'event': event_type,
103+
'user_id': instance.uid,
104+
'email': user_email,
105+
'resource_id': instance.id,
106+
'timestamp': timezone.now().isoformat(),
107+
}
108+
109+
if sender == Question:
110+
payload['category'] = instance.category
111+
payload['tutorial'] = instance.tutorial
112+
payload['title'] = instance.title
113+
elif sender == Answer:
114+
payload['question_id'] = instance.question_id
115+
payload['category'] = instance.question.category
116+
payload['tutorial'] = instance.question.tutorial
117+
118+
return payload
119+
120+
121+
def webhook_on_create(sender, instance, created, **kwargs):
122+
"""Signal handler: fires when a Question or Answer is saved for the first time."""
123+
if not created:
124+
return # We only care about new objects, not updates
125+
126+
from .models import Question, Answer
127+
128+
if sender == Question:
129+
event = 'question_created'
130+
elif sender == Answer:
131+
event = 'answer_created'
132+
else:
133+
return
134+
135+
payload = _build_payload(event, instance, sender)
136+
_send_webhook(payload)
137+
138+
139+
def webhook_on_delete(sender, instance, **kwargs):
140+
"""Signal handler: fires when a Question or Answer is deleted."""
141+
from .models import Question, Answer
142+
143+
if sender == Question:
144+
event = 'question_deleted'
145+
elif sender == Answer:
146+
event = 'answer_deleted'
147+
else:
148+
return
149+
150+
payload = _build_payload(event, instance, sender)
151+
_send_webhook(payload)

0 commit comments

Comments
 (0)