habulaj commited on
Commit
4586a84
·
verified ·
1 Parent(s): 27dff6a

Update routes/sendnotifications.py

Browse files
Files changed (1) hide show
  1. routes/sendnotifications.py +71 -71
routes/sendnotifications.py CHANGED
@@ -9,7 +9,7 @@ from google.auth.transport.requests import Request
9
  router = APIRouter()
10
 
11
  # 🔧 Supabase Config
12
- SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co" # ajuste para seu Supabase
13
  SUPABASE_KEY = os.getenv("SUPA_KEY")
14
  SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
15
 
@@ -35,13 +35,13 @@ FCM_PROJECT_ID = "closetcoach-2d50b"
35
 
36
  class NotificationRequest(BaseModel):
37
  keyword: str
38
- target_user_id: str
39
  reference: str = ""
40
 
41
  def short_collapse_key(keyword: str, sender_id: str, receiver_id: str) -> str:
42
  raw = f"{keyword}:{sender_id}:{receiver_id}"
43
  return hashlib.sha1(raw.encode()).hexdigest()[:20]
44
-
45
  async def verify_user_token(user_token: str) -> str:
46
  headers = {
47
  "Authorization": f"Bearer {user_token}",
@@ -52,47 +52,24 @@ async def verify_user_token(user_token: str) -> str:
52
  async with aiohttp.ClientSession() as session:
53
  async with session.get(f"{SUPABASE_URL}/auth/v1/user", headers=headers) as response:
54
  if response.status != 200:
55
- raise HTTPException(status_code=401, detail="Token inválido ou expirado")
56
 
57
  user_data = await response.json()
58
  user_id = user_data.get("id")
59
  if not user_id:
60
- raise HTTPException(status_code=400, detail="ID do usuário não encontrado")
61
 
62
  return user_id
63
 
64
- async def get_post_info(feed_id: str):
65
- # Busca o feed
66
- feeds = await fetch_supabase("Feeds", "description,portfolios,user_id", {"id": feed_id})
67
- if not feeds:
68
- raise HTTPException(status_code=404, detail="Post not found")
69
-
70
- feed = feeds[0]
71
- description = feed.get("description", "")
72
- portfolio_ids = feed.get("portfolios") or []
73
- user_id = feed.get("user_id")
74
-
75
- image_url = None
76
- if portfolio_ids:
77
- portfolio_data = await fetch_supabase("Portfolio", "image_url", {"id": portfolio_ids[0]})
78
- if portfolio_data:
79
- image_url = portfolio_data[0].get("image_url")
80
-
81
- return {
82
- "description": description,
83
- "image_url": image_url,
84
- "user_id": user_id # <-- importante
85
- }
86
-
87
  async def fetch_supabase(table: str, select: str, filters: dict, headers=SUPABASE_ROLE_HEADERS):
88
  filter_query = '&'.join([f'{k}=eq.{v}' for k, v in filters.items()])
89
- url = f"{SUPABASE_URL}/rest/v1/{table}?select={select}&{filter_query}"
90
 
91
  async with aiohttp.ClientSession() as session:
92
  async with session.get(url, headers=headers) as resp:
93
  if resp.status != 200:
94
  detail = await resp.text()
95
- raise HTTPException(status_code=500, detail=f"Erro Supabase: {detail}")
96
  return await resp.json()
97
 
98
  def format_name(full_name: str) -> str:
@@ -111,41 +88,6 @@ async def check_follow_exists(follower_id: str, following_id: str) -> bool:
111
  result = await fetch_supabase("followers", "id", {"follower_id": follower_id, "following_id": following_id})
112
  return len(result) > 0
113
 
114
- def get_access_token():
115
- credentials = service_account.Credentials.from_service_account_file(
116
- SERVICE_ACCOUNT_FILE
117
- )
118
- scoped_credentials = credentials.with_scopes(
119
- ['https://www.googleapis.com/auth/firebase.messaging']
120
- )
121
- scoped_credentials.refresh(Request())
122
- return scoped_credentials.token
123
-
124
- async def send_fcm_notification(title: str, body: str, token: str):
125
- payload = {
126
- "message": {
127
- "notification": {
128
- "title": title,
129
- "body": body,
130
- },
131
- "token": token
132
- }
133
- }
134
-
135
- access_token = get_access_token()
136
- headers = {
137
- "Authorization": f"Bearer {access_token}",
138
- "Content-Type": "application/json"
139
- }
140
- url = f"https://fcm.googleapis.com/v1/projects/{FCM_PROJECT_ID}/messages:send"
141
-
142
- async with aiohttp.ClientSession() as session:
143
- async with session.post(url, headers=headers, json=payload) as resp:
144
- resp_text = await resp.text()
145
- if resp.status != 200:
146
- raise HTTPException(status_code=resp.status, detail=f"FCM error: {resp_text}")
147
- return await resp.json()
148
-
149
  async def check_subscription_exists(customer_id: str, stylist_id: str) -> bool:
150
  result = await fetch_supabase(
151
  "Subscriptions",
@@ -158,46 +100,99 @@ async def check_subscription_exists(customer_id: str, stylist_id: str) -> bool:
158
  )
159
  return len(result) > 0
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  @router.post("/send-notification")
162
  async def send_notification(
163
  data: NotificationRequest,
164
  user_token: str = Header(..., alias="User-key")
165
  ):
166
- follower_id = await verify_user_token(user_token)
167
 
168
- if data.keyword not in ("follow", "like", "subscriber"):
169
  raise HTTPException(status_code=400, detail="Unsupported keyword")
170
 
 
171
  if data.keyword == "like":
172
  post_info = await get_post_info(data.reference)
173
  target_user_id = post_info["user_id"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  else:
175
  target_user_id = data.target_user_id
176
 
 
177
  if data.keyword == "follow":
178
- follow_exists = await check_follow_exists(follower_id, target_user_id)
179
  if not follow_exists:
180
  raise HTTPException(status_code=403, detail="Follow relationship does not exist")
181
 
182
  if data.keyword == "subscriber":
183
  subscription_exists = await check_subscription_exists(
184
- customer_id=follower_id,
185
  stylist_id=target_user_id
186
  )
187
  if not subscription_exists:
188
  return {"detail": "No active subscription found, notification not sent"}
189
 
 
190
  target_user = await get_user_info(target_user_id)
191
  if not target_user or not target_user.get("token_fcm"):
192
  raise HTTPException(status_code=404, detail="Target user or FCM token not found")
193
 
194
- actor_info = await get_user_info(follower_id)
195
  if not actor_info or not actor_info.get("name"):
196
  raise HTTPException(status_code=404, detail="User not found")
197
  actor_name = format_name(actor_info["name"])
198
 
199
- collapse_id = short_collapse_key(data.keyword, follower_id, target_user_id)
200
 
 
201
  if data.keyword == "follow":
202
  title = "🎉 New Follower!"
203
  body = f"{actor_name} started following you."
@@ -211,9 +206,14 @@ async def send_notification(
211
  title = "💼 New Subscriber!"
212
  body = f"{actor_name} just subscribed to your styling services."
213
  image_url = None
 
 
 
 
214
  else:
215
  raise HTTPException(status_code=400, detail="Unsupported keyword")
216
 
 
217
  message = {
218
  "notification": {
219
  "title": title,
 
9
  router = APIRouter()
10
 
11
  # 🔧 Supabase Config
12
+ SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
13
  SUPABASE_KEY = os.getenv("SUPA_KEY")
14
  SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
15
 
 
35
 
36
  class NotificationRequest(BaseModel):
37
  keyword: str
38
+ target_user_id: str = "" # optional for 'newmessage'
39
  reference: str = ""
40
 
41
  def short_collapse_key(keyword: str, sender_id: str, receiver_id: str) -> str:
42
  raw = f"{keyword}:{sender_id}:{receiver_id}"
43
  return hashlib.sha1(raw.encode()).hexdigest()[:20]
44
+
45
  async def verify_user_token(user_token: str) -> str:
46
  headers = {
47
  "Authorization": f"Bearer {user_token}",
 
52
  async with aiohttp.ClientSession() as session:
53
  async with session.get(f"{SUPABASE_URL}/auth/v1/user", headers=headers) as response:
54
  if response.status != 200:
55
+ raise HTTPException(status_code=401, detail="Invalid or expired token")
56
 
57
  user_data = await response.json()
58
  user_id = user_data.get("id")
59
  if not user_id:
60
+ raise HTTPException(status_code=400, detail="User ID not found")
61
 
62
  return user_id
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  async def fetch_supabase(table: str, select: str, filters: dict, headers=SUPABASE_ROLE_HEADERS):
65
  filter_query = '&'.join([f'{k}=eq.{v}' for k, v in filters.items()])
66
+ url = f"{SUPABASE_URL}/rest/v1/{table}?select={select}&{filter_query}&order=created_at.desc"
67
 
68
  async with aiohttp.ClientSession() as session:
69
  async with session.get(url, headers=headers) as resp:
70
  if resp.status != 200:
71
  detail = await resp.text()
72
+ raise HTTPException(status_code=500, detail=f"Supabase error: {detail}")
73
  return await resp.json()
74
 
75
  def format_name(full_name: str) -> str:
 
88
  result = await fetch_supabase("followers", "id", {"follower_id": follower_id, "following_id": following_id})
89
  return len(result) > 0
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  async def check_subscription_exists(customer_id: str, stylist_id: str) -> bool:
92
  result = await fetch_supabase(
93
  "Subscriptions",
 
100
  )
101
  return len(result) > 0
102
 
103
+ async def get_post_info(feed_id: str):
104
+ feeds = await fetch_supabase("Feeds", "description,portfolios,user_id", {"id": feed_id})
105
+ if not feeds:
106
+ raise HTTPException(status_code=404, detail="Post not found")
107
+
108
+ feed = feeds[0]
109
+ description = feed.get("description", "")
110
+ portfolio_ids = feed.get("portfolios") or []
111
+ user_id = feed.get("user_id")
112
+
113
+ image_url = None
114
+ if portfolio_ids:
115
+ portfolio_data = await fetch_supabase("Portfolio", "image_url", {"id": portfolio_ids[0]})
116
+ if portfolio_data:
117
+ image_url = portfolio_data[0].get("image_url")
118
+
119
+ return {
120
+ "description": description,
121
+ "image_url": image_url,
122
+ "user_id": user_id
123
+ }
124
+
125
+ def get_access_token():
126
+ credentials = service_account.Credentials.from_service_account_file(
127
+ SERVICE_ACCOUNT_FILE
128
+ )
129
+ scoped_credentials = credentials.with_scopes(
130
+ ['https://www.googleapis.com/auth/firebase.messaging']
131
+ )
132
+ scoped_credentials.refresh(Request())
133
+ return scoped_credentials.token
134
+
135
  @router.post("/send-notification")
136
  async def send_notification(
137
  data: NotificationRequest,
138
  user_token: str = Header(..., alias="User-key")
139
  ):
140
+ sender_id = await verify_user_token(user_token)
141
 
142
+ if data.keyword not in ("follow", "like", "subscriber", "newmessage"):
143
  raise HTTPException(status_code=400, detail="Unsupported keyword")
144
 
145
+ # Determine target_user_id
146
  if data.keyword == "like":
147
  post_info = await get_post_info(data.reference)
148
  target_user_id = post_info["user_id"]
149
+ elif data.keyword == "newmessage":
150
+ # Get last message in the chat
151
+ messages = await fetch_supabase("messages", "sender_id,content", {"chat_id": data.reference})
152
+ if not messages:
153
+ raise HTTPException(status_code=404, detail="No messages found in chat")
154
+
155
+ last_message = messages[0]
156
+ sender_id = last_message["sender_id"]
157
+ content = last_message["content"]
158
+
159
+ # Get participants
160
+ chats = await fetch_supabase("chats", "client_id,stylist_id", {"id": data.reference})
161
+ if not chats:
162
+ raise HTTPException(status_code=404, detail="Chat not found")
163
+
164
+ chat = chats[0]
165
+ target_user_id = chat["stylist_id"] if sender_id == chat["client_id"] else chat["client_id"]
166
  else:
167
  target_user_id = data.target_user_id
168
 
169
+ # Check conditions
170
  if data.keyword == "follow":
171
+ follow_exists = await check_follow_exists(sender_id, target_user_id)
172
  if not follow_exists:
173
  raise HTTPException(status_code=403, detail="Follow relationship does not exist")
174
 
175
  if data.keyword == "subscriber":
176
  subscription_exists = await check_subscription_exists(
177
+ customer_id=sender_id,
178
  stylist_id=target_user_id
179
  )
180
  if not subscription_exists:
181
  return {"detail": "No active subscription found, notification not sent"}
182
 
183
+ # User info
184
  target_user = await get_user_info(target_user_id)
185
  if not target_user or not target_user.get("token_fcm"):
186
  raise HTTPException(status_code=404, detail="Target user or FCM token not found")
187
 
188
+ actor_info = await get_user_info(sender_id)
189
  if not actor_info or not actor_info.get("name"):
190
  raise HTTPException(status_code=404, detail="User not found")
191
  actor_name = format_name(actor_info["name"])
192
 
193
+ collapse_id = short_collapse_key(data.keyword, sender_id, target_user_id)
194
 
195
+ # Notification content
196
  if data.keyword == "follow":
197
  title = "🎉 New Follower!"
198
  body = f"{actor_name} started following you."
 
206
  title = "💼 New Subscriber!"
207
  body = f"{actor_name} just subscribed to your styling services."
208
  image_url = None
209
+ elif data.keyword == "newmessage":
210
+ title = "💬 New Message"
211
+ body = f"{actor_name}: {content}"
212
+ image_url = None
213
  else:
214
  raise HTTPException(status_code=400, detail="Unsupported keyword")
215
 
216
+ # Compose FCM message
217
  message = {
218
  "notification": {
219
  "title": title,