Update routes/sendnotifications.py
Browse files- routes/sendnotifications.py +74 -54
routes/sendnotifications.py
CHANGED
@@ -36,6 +36,7 @@ FCM_PROJECT_ID = "closetcoach-2d50b"
|
|
36 |
class NotificationRequest(BaseModel):
|
37 |
keyword: str
|
38 |
target_user_id: str
|
|
|
39 |
|
40 |
def short_collapse_key(follower_id: str, target_user_id: str) -> str:
|
41 |
raw = f"follow:{follower_id}:{target_user_id}"
|
@@ -60,6 +61,27 @@ async def verify_user_token(user_token: str) -> str:
|
|
60 |
|
61 |
return user_id
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
async def fetch_supabase(table: str, select: str, filters: dict, headers=SUPABASE_ROLE_HEADERS):
|
64 |
filter_query = '&'.join([f'{k}=eq.{v}' for k, v in filters.items()])
|
65 |
url = f"{SUPABASE_URL}/rest/v1/{table}?select={select}&{filter_query}"
|
@@ -130,72 +152,70 @@ async def send_notification(
|
|
130 |
follower_id = await verify_user_token(user_token)
|
131 |
target_user_id = data.target_user_id
|
132 |
|
133 |
-
if data.keyword
|
134 |
raise HTTPException(status_code=400, detail="Unsupported keyword")
|
135 |
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
f"{SUPABASE_URL}/rest/v1/User?select=name,token_fcm&id=eq.{target_user_id}",
|
140 |
-
headers=SUPABASE_ROLE_HEADERS
|
141 |
-
)
|
142 |
-
if target_user_resp.status != 200:
|
143 |
-
raise HTTPException(status_code=500, detail="Failed to fetch target user")
|
144 |
-
target_users = await target_user_resp.json()
|
145 |
-
if not target_users or not target_users[0].get("token_fcm"):
|
146 |
-
raise HTTPException(status_code=404, detail="Target user or FCM token not found")
|
147 |
-
target_user = target_users[0]
|
148 |
-
|
149 |
-
# Verifica se follow existe
|
150 |
-
follow_resp = await session.get(
|
151 |
-
f"{SUPABASE_URL}/rest/v1/followers?select=id&follower_id=eq.{follower_id}&following_id=eq.{target_user_id}",
|
152 |
-
headers=SUPABASE_ROLE_HEADERS
|
153 |
-
)
|
154 |
-
if follow_resp.status != 200:
|
155 |
-
raise HTTPException(status_code=500, detail="Failed to verify follow relationship")
|
156 |
-
follow_exists = len(await follow_resp.json()) > 0
|
157 |
if not follow_exists:
|
158 |
raise HTTPException(status_code=403, detail="Follow relationship does not exist")
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
follower_name = format_name(followers[0]["name"])
|
171 |
-
|
172 |
-
# Monta notificação
|
173 |
-
title = "🎉 New Follower!"
|
174 |
-
body = f"{follower_name} started following you."
|
175 |
|
176 |
collapse_id = short_collapse_key(follower_id, target_user_id)
|
177 |
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
"notification": {
|
181 |
-
"
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
"
|
186 |
-
"
|
187 |
-
"notification": {
|
188 |
-
"tag": collapse_id # 🔥 ESSENCIAL para evitar duplicata visual
|
189 |
-
}
|
190 |
-
},
|
191 |
-
"apns": {
|
192 |
-
"headers": {
|
193 |
-
"apns-collapse-id": collapse_id
|
194 |
-
}
|
195 |
}
|
196 |
}
|
197 |
}
|
198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
access_token = get_access_token()
|
200 |
headers = {
|
201 |
"Authorization": f"Bearer {access_token}",
|
@@ -210,4 +230,4 @@ async def send_notification(
|
|
210 |
raise HTTPException(status_code=resp.status, detail=f"FCM error: {resp_text}")
|
211 |
fcm_response = await resp.json()
|
212 |
|
213 |
-
return {"detail": "
|
|
|
36 |
class NotificationRequest(BaseModel):
|
37 |
keyword: str
|
38 |
target_user_id: str
|
39 |
+
reference: str = ""
|
40 |
|
41 |
def short_collapse_key(follower_id: str, target_user_id: str) -> str:
|
42 |
raw = f"follow:{follower_id}:{target_user_id}"
|
|
|
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 = eval(feed.get("portfolios", "[]"))
|
73 |
+
|
74 |
+
image_url = None
|
75 |
+
if portfolio_ids:
|
76 |
+
portfolio_data = await fetch_supabase("Portfolio", "image_url", {"id": portfolio_ids[0]})
|
77 |
+
if portfolio_data:
|
78 |
+
image_url = portfolio_data[0].get("image_url")
|
79 |
+
|
80 |
+
return {
|
81 |
+
"description": description,
|
82 |
+
"image_url": image_url
|
83 |
+
}
|
84 |
+
|
85 |
async def fetch_supabase(table: str, select: str, filters: dict, headers=SUPABASE_ROLE_HEADERS):
|
86 |
filter_query = '&'.join([f'{k}=eq.{v}' for k, v in filters.items()])
|
87 |
url = f"{SUPABASE_URL}/rest/v1/{table}?select={select}&{filter_query}"
|
|
|
152 |
follower_id = await verify_user_token(user_token)
|
153 |
target_user_id = data.target_user_id
|
154 |
|
155 |
+
if data.keyword not in ("follow", "like"):
|
156 |
raise HTTPException(status_code=400, detail="Unsupported keyword")
|
157 |
|
158 |
+
# Verifica se relação de follow existe
|
159 |
+
if data.keyword == "follow":
|
160 |
+
follow_exists = await check_follow_exists(follower_id, target_user_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
if not follow_exists:
|
162 |
raise HTTPException(status_code=403, detail="Follow relationship does not exist")
|
163 |
|
164 |
+
# Busca FCM e nome do usuário de destino
|
165 |
+
target_user = await get_user_info(target_user_id)
|
166 |
+
if not target_user or not target_user.get("token_fcm"):
|
167 |
+
raise HTTPException(status_code=404, detail="Target user or FCM token not found")
|
168 |
+
|
169 |
+
# Busca nome do autor da ação
|
170 |
+
actor_info = await get_user_info(follower_id)
|
171 |
+
if not actor_info or not actor_info.get("name"):
|
172 |
+
raise HTTPException(status_code=404, detail="User not found")
|
173 |
+
actor_name = format_name(actor_info["name"])
|
|
|
|
|
|
|
|
|
|
|
174 |
|
175 |
collapse_id = short_collapse_key(follower_id, target_user_id)
|
176 |
|
177 |
+
# Monta conteúdo da notificação
|
178 |
+
if data.keyword == "follow":
|
179 |
+
title = "🎉 New Follower!"
|
180 |
+
body = f"{actor_name} started following you."
|
181 |
+
image_url = None
|
182 |
+
elif data.keyword == "like":
|
183 |
+
# Pega dados do post
|
184 |
+
post_info = await get_post_info(data.reference)
|
185 |
+
title = "❤️ New Like!"
|
186 |
+
desc = post_info["description"]
|
187 |
+
body = f"{actor_name} liked your post" + (f": \"{desc}\"" if desc else ".")
|
188 |
+
image_url = post_info["image_url"]
|
189 |
+
else:
|
190 |
+
raise HTTPException(status_code=400, detail="Unsupported keyword")
|
191 |
+
|
192 |
+
# Monta payload
|
193 |
+
message = {
|
194 |
+
"notification": {
|
195 |
+
"title": title,
|
196 |
+
"body": body,
|
197 |
+
},
|
198 |
+
"token": target_user["token_fcm"],
|
199 |
+
"android": {
|
200 |
+
"collapse_key": collapse_id,
|
201 |
"notification": {
|
202 |
+
"tag": collapse_id
|
203 |
+
}
|
204 |
+
},
|
205 |
+
"apns": {
|
206 |
+
"headers": {
|
207 |
+
"apns-collapse-id": collapse_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
208 |
}
|
209 |
}
|
210 |
}
|
211 |
|
212 |
+
if image_url:
|
213 |
+
message["notification"]["image"] = image_url
|
214 |
+
message["android"]["notification"]["image"] = image_url
|
215 |
+
|
216 |
+
payload = {"message": message}
|
217 |
+
|
218 |
+
# Envia via FCM
|
219 |
access_token = get_access_token()
|
220 |
headers = {
|
221 |
"Authorization": f"Bearer {access_token}",
|
|
|
230 |
raise HTTPException(status_code=resp.status, detail=f"FCM error: {resp_text}")
|
231 |
fcm_response = await resp.json()
|
232 |
|
233 |
+
return {"detail": "Notification sent successfully", "fcm_response": fcm_response}
|