Update routes/stylist.py
Browse files- routes/stylist.py +70 -117
routes/stylist.py
CHANGED
@@ -357,6 +357,76 @@ def get_courtesy_consultations(user_id: str) -> Dict[str, Any]:
|
|
357 |
"monthly_courtesy_consultations": list(monthly_data.values())
|
358 |
}
|
359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
def get_monthly_likes(user_id: str) -> Dict[str, Any]:
|
361 |
ny_timezone = pytz.timezone('America/New_York')
|
362 |
now_ny = datetime.now(ny_timezone)
|
@@ -380,19 +450,6 @@ def get_monthly_likes(user_id: str) -> Dict[str, Any]:
|
|
380 |
# Calcular data de início (6 meses atrás)
|
381 |
start_date = (now_ny - relativedelta(months=6)).strftime('%Y-%m-%d')
|
382 |
|
383 |
-
def parse_datetime_safely(date_str: str) -> datetime:
|
384 |
-
"""Parse datetime string safely, handling microseconds overflow and timezone formats"""
|
385 |
-
try:
|
386 |
-
original_date_str = date_str
|
387 |
-
|
388 |
-
# Remove 'Z' e substitui por '+00:00' se necessário
|
389 |
-
if date_str.endswith('Z'):
|
390 |
-
date_str = date_str.replace('Z', '+00:00')
|
391 |
-
|
392 |
-
# Normalizar timezone de -04 para -04:00
|
393 |
-
import re
|
394 |
-
tz_pattern = r'([+-])(\d{2})$'
|
395 |
-
|
396 |
try:
|
397 |
# Primeiro, obter todos os feed_items do usuário
|
398 |
feeds_url = f"{SUPABASE_URL}/rest/v1/Feeds?user_id=eq.{user_id}"
|
@@ -452,110 +509,6 @@ def get_monthly_likes(user_id: str) -> Dict[str, Any]:
|
|
452 |
"monthly_likes": list(monthly_data.values()),
|
453 |
"total_likes_last_6_months": 0
|
454 |
}
|
455 |
-
match = re.search(tz_pattern, date_str)
|
456 |
-
if match:
|
457 |
-
sign, hours = match.groups()
|
458 |
-
date_str = re.sub(tz_pattern, f'{sign}{hours}:00', date_str)
|
459 |
-
|
460 |
-
# Verificar se há microssegundos com mais de 6 dígitos
|
461 |
-
if '.' in date_str:
|
462 |
-
# Separar a parte dos microssegundos
|
463 |
-
parts = date_str.split('.')
|
464 |
-
if len(parts) == 2:
|
465 |
-
base_part = parts[0]
|
466 |
-
microsec_and_tz = parts[1]
|
467 |
-
|
468 |
-
# Encontrar onde começa o timezone (+ ou - após os dígitos)
|
469 |
-
tz_start_idx = -1
|
470 |
-
for i in range(len(microsec_and_tz)):
|
471 |
-
if microsec_and_tz[i] in ['+', '-']:
|
472 |
-
tz_start_idx = i
|
473 |
-
break
|
474 |
-
|
475 |
-
if tz_start_idx > 0:
|
476 |
-
# Há timezone
|
477 |
-
microsec_part = microsec_and_tz[:tz_start_idx]
|
478 |
-
tz_part = microsec_and_tz[tz_start_idx:]
|
479 |
-
|
480 |
-
# Limitar microssegundos a 6 dígitos
|
481 |
-
if len(microsec_part) > 6:
|
482 |
-
microsec_part = microsec_part[:6]
|
483 |
-
|
484 |
-
date_str = f"{base_part}.{microsec_part}{tz_part}"
|
485 |
-
else:
|
486 |
-
# Não há timezone, apenas microssegundos
|
487 |
-
if len(microsec_and_tz) > 6:
|
488 |
-
microsec_and_tz = microsec_and_tz[:6]
|
489 |
-
date_str = f"{base_part}.{microsec_and_tz}"
|
490 |
-
|
491 |
-
parsed_date = datetime.fromisoformat(date_str)
|
492 |
-
logger.debug(f"✅ Successfully parsed date '{original_date_str}' -> '{date_str}' -> {parsed_date}")
|
493 |
-
return parsed_date
|
494 |
-
|
495 |
-
except ValueError as e:
|
496 |
-
logger.warning(f"⚠️ Error parsing date '{original_date_str}': {str(e)}")
|
497 |
-
# Tentar um parsing mais simples como fallback
|
498 |
-
try:
|
499 |
-
# Remover microssegundos completamente e tentar novamente
|
500 |
-
simple_date = original_date_str.split('.')[0]
|
501 |
-
# Adicionar timezone padrão se não tiver
|
502 |
-
if not any(tz in simple_date for tz in ['+', '-', 'Z']):
|
503 |
-
simple_date += '-04:00'
|
504 |
-
elif simple_date.endswith('-04'):
|
505 |
-
simple_date += ':00'
|
506 |
-
return datetime.fromisoformat(simple_date)
|
507 |
-
except:
|
508 |
-
logger.error(f"❌ Failed to parse date '{original_date_str}' with fallback method")
|
509 |
-
# Último recurso: retornar data atual
|
510 |
-
return datetime.now(pytz.UTC)
|
511 |
-
|
512 |
-
try:
|
513 |
-
# Primeiro, obter todos os feed_items do usuário
|
514 |
-
feeds_url = f"{SUPABASE_URL}/rest/v1/Feeds?user_id=eq.{user_id}"
|
515 |
-
feeds_response = requests.get(feeds_url, headers=SUPABASE_HEADERS)
|
516 |
-
|
517 |
-
if feeds_response.status_code == 200:
|
518 |
-
feeds = feeds_response.json()
|
519 |
-
feed_ids = [feed.get("id") for feed in feeds]
|
520 |
-
|
521 |
-
if feed_ids:
|
522 |
-
# Construir a condição para os feed_ids
|
523 |
-
# (não podemos usar in.() diretamente por limitações da API, então vamos fazer uma string)
|
524 |
-
feed_ids_str = ','.join([str(id) for id in feed_ids])
|
525 |
-
|
526 |
-
# Obter likes para esses feeds no intervalo de tempo
|
527 |
-
likes_url = f"{SUPABASE_URL}/rest/v1/likes?feed_item_id=in.({feed_ids_str})&created_at=gte.{start_date}"
|
528 |
-
likes_response = requests.get(likes_url, headers=SUPABASE_HEADERS)
|
529 |
-
|
530 |
-
if likes_response.status_code == 200:
|
531 |
-
likes = likes_response.json()
|
532 |
-
|
533 |
-
# Contar likes por mês
|
534 |
-
for like in likes:
|
535 |
-
# Converter a data para o fuso horário de NY usando o parse seguro
|
536 |
-
like_date_str = like.get("created_at")
|
537 |
-
if like_date_str:
|
538 |
-
like_date = parse_datetime_safely(like_date_str).astimezone(ny_timezone)
|
539 |
-
|
540 |
-
month_key = f"{like_date.year}-{like_date.month}"
|
541 |
-
if month_key in monthly_data:
|
542 |
-
monthly_data[month_key]["likes_count"] += 1
|
543 |
-
total_likes_last_6_months += 1
|
544 |
-
|
545 |
-
# Convertendo para lista e ordenando por mês (do mais recente para o mais antigo)
|
546 |
-
result = list(monthly_data.values())
|
547 |
-
result.sort(key=lambda x: (now_ny.year * 12 + now_ny.month - (x["month"])) % 12)
|
548 |
-
|
549 |
-
return {
|
550 |
-
"monthly_likes": result,
|
551 |
-
"total_likes_last_6_months": total_likes_last_6_months
|
552 |
-
}
|
553 |
-
except Exception as e:
|
554 |
-
logger.error(f"❌ Error getting monthly likes: {str(e)}")
|
555 |
-
return {
|
556 |
-
"monthly_likes": list(monthly_data.values()),
|
557 |
-
"total_likes_last_6_months": 0
|
558 |
-
}
|
559 |
|
560 |
@router.post("/bank_account_dashboard_link")
|
561 |
async def generate_bank_account_dashboard_link(data: UserIDRequest):
|
|
|
357 |
"monthly_courtesy_consultations": list(monthly_data.values())
|
358 |
}
|
359 |
|
360 |
+
def parse_datetime_safely(date_str: str) -> datetime:
|
361 |
+
"""Parse datetime string safely, handling microseconds overflow and timezone formats"""
|
362 |
+
try:
|
363 |
+
original_date_str = date_str
|
364 |
+
|
365 |
+
# Remove 'Z' e substitui por '+00:00' se necessário
|
366 |
+
if date_str.endswith('Z'):
|
367 |
+
date_str = date_str.replace('Z', '+00:00')
|
368 |
+
|
369 |
+
# Normalizar timezone de -04 para -04:00
|
370 |
+
import re
|
371 |
+
tz_pattern = r'([+-])(\d{2})$'
|
372 |
+
match = re.search(tz_pattern, date_str)
|
373 |
+
if match:
|
374 |
+
sign, hours = match.groups()
|
375 |
+
date_str = re.sub(tz_pattern, f'{sign}{hours}:00', date_str)
|
376 |
+
|
377 |
+
# Verificar se há microssegundos com mais de 6 dígitos
|
378 |
+
if '.' in date_str:
|
379 |
+
# Separar a parte dos microssegundos
|
380 |
+
parts = date_str.split('.')
|
381 |
+
if len(parts) == 2:
|
382 |
+
base_part = parts[0]
|
383 |
+
microsec_and_tz = parts[1]
|
384 |
+
|
385 |
+
# Encontrar onde começa o timezone (+ ou - após os dígitos)
|
386 |
+
tz_start_idx = -1
|
387 |
+
for i in range(len(microsec_and_tz)):
|
388 |
+
if microsec_and_tz[i] in ['+', '-']:
|
389 |
+
tz_start_idx = i
|
390 |
+
break
|
391 |
+
|
392 |
+
if tz_start_idx > 0:
|
393 |
+
# Há timezone
|
394 |
+
microsec_part = microsec_and_tz[:tz_start_idx]
|
395 |
+
tz_part = microsec_and_tz[tz_start_idx:]
|
396 |
+
|
397 |
+
# Limitar microssegundos a 6 dígitos
|
398 |
+
if len(microsec_part) > 6:
|
399 |
+
microsec_part = microsec_part[:6]
|
400 |
+
|
401 |
+
date_str = f"{base_part}.{microsec_part}{tz_part}"
|
402 |
+
else:
|
403 |
+
# Não há timezone, apenas microssegundos
|
404 |
+
if len(microsec_and_tz) > 6:
|
405 |
+
microsec_and_tz = microsec_and_tz[:6]
|
406 |
+
date_str = f"{base_part}.{microsec_and_tz}"
|
407 |
+
|
408 |
+
parsed_date = datetime.fromisoformat(date_str)
|
409 |
+
logger.debug(f"✅ Successfully parsed date '{original_date_str}' -> '{date_str}' -> {parsed_date}")
|
410 |
+
return parsed_date
|
411 |
+
|
412 |
+
except ValueError as e:
|
413 |
+
logger.warning(f"⚠️ Error parsing date '{original_date_str}': {str(e)}")
|
414 |
+
# Tentar um parsing mais simples como fallback
|
415 |
+
try:
|
416 |
+
# Remover microssegundos completamente e tentar novamente
|
417 |
+
simple_date = original_date_str.split('.')[0]
|
418 |
+
# Adicionar timezone padrão se não tiver
|
419 |
+
if not any(tz in simple_date for tz in ['+', '-', 'Z']):
|
420 |
+
simple_date += '-04:00'
|
421 |
+
elif simple_date.endswith('-04'):
|
422 |
+
simple_date += ':00'
|
423 |
+
return datetime.fromisoformat(simple_date)
|
424 |
+
except:
|
425 |
+
logger.error(f"❌ Failed to parse date '{original_date_str}' with fallback method")
|
426 |
+
# Último recurso: retornar data atual
|
427 |
+
return datetime.now(pytz.UTC)
|
428 |
+
|
429 |
+
|
430 |
def get_monthly_likes(user_id: str) -> Dict[str, Any]:
|
431 |
ny_timezone = pytz.timezone('America/New_York')
|
432 |
now_ny = datetime.now(ny_timezone)
|
|
|
450 |
# Calcular data de início (6 meses atrás)
|
451 |
start_date = (now_ny - relativedelta(months=6)).strftime('%Y-%m-%d')
|
452 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
453 |
try:
|
454 |
# Primeiro, obter todos os feed_items do usuário
|
455 |
feeds_url = f"{SUPABASE_URL}/rest/v1/Feeds?user_id=eq.{user_id}"
|
|
|
509 |
"monthly_likes": list(monthly_data.values()),
|
510 |
"total_likes_last_6_months": 0
|
511 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
512 |
|
513 |
@router.post("/bank_account_dashboard_link")
|
514 |
async def generate_bank_account_dashboard_link(data: UserIDRequest):
|