1+ import json
2+ import logging
3+ import threading
4+
5+ import requests
6+ from django .conf import settings
17from django .utils import timezone
28from django .core .cache import cache
39
10+ logger = logging .getLogger (__name__ )
11+
412
513HOME_CACHE_KEYS = [
614 'home:categories' ,
@@ -35,4 +43,109 @@ def last_active_signal_from_reply(sender, instance, created, **kwargs):
3543
3644
3745def 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