Fixes and tickets
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
exports.GEMINI_API_KEY = "AIzaSyBoPQC5ZaMKP73TlKGpZQp1mAw8ArHiH9Y";
|
||||
// exports.SUNO_API_KEY = "543b5650b30d93e98d8046a830244c80"; client api key
|
||||
exports.SUNO_API_KEY = "138bace8405b869f917156440acb6fed"; // perso api key
|
||||
exports.SUNO_API_KEY = "5a689606d3c59f58268b4963d5107361"; // api key
|
||||
|
||||
exports.RESEND_API_KEY = "re_";
|
||||
|
||||
Executable
+746
@@ -0,0 +1,746 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script interactif pour supprimer toutes les Cloud Functions d'un projet Firebase
|
||||
# Utilise les flèches pour naviguer et Entrée pour sélectionner
|
||||
|
||||
# Couleurs pour l'affichage
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Fonction pour afficher le menu avec sélection
|
||||
show_menu() {
|
||||
local options=("$@")
|
||||
local selected=0
|
||||
local num_options=${#options[@]}
|
||||
|
||||
while true; do
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Gestionnaire Cloud Functions Firebase ===${NC}\n"
|
||||
|
||||
if [ ! -z "$CURRENT_PROJECT" ]; then
|
||||
echo -e "${GREEN}Projet actuel: ${BOLD}$CURRENT_PROJECT${NC}\n"
|
||||
fi
|
||||
|
||||
echo "Utilisez les flèches ↑↓ pour naviguer et Entrée pour sélectionner:\n"
|
||||
|
||||
for i in "${!options[@]}"; do
|
||||
if [ $i -eq $selected ]; then
|
||||
echo -e "${GREEN}► ${options[$i]}${NC}"
|
||||
else
|
||||
echo -e " ${options[$i]}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Lire une seule touche
|
||||
read -rsn1 key
|
||||
|
||||
case $key in
|
||||
$'\033') # Séquence d'échappement pour les flèches
|
||||
read -rsn2 key
|
||||
case $key in
|
||||
'[A') # Flèche haut
|
||||
((selected--))
|
||||
if [ $selected -lt 0 ]; then
|
||||
selected=$((num_options-1))
|
||||
fi
|
||||
;;
|
||||
'[B') # Flèche bas
|
||||
((selected++))
|
||||
if [ $selected -ge $num_options ]; then
|
||||
selected=0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
'') # Entrée
|
||||
return $selected
|
||||
;;
|
||||
'q'|'Q') # Quitter
|
||||
echo -e "\n${YELLOW}Au revoir!${NC}"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Fonction pour vérifier si gcloud est installé
|
||||
check_gcloud() {
|
||||
if ! command -v gcloud &> /dev/null; then
|
||||
echo -e "${RED}Erreur: gcloud CLI n'est pas installé.${NC}"
|
||||
echo "Veuillez installer Google Cloud SDK: https://cloud.google.com/sdk/docs/install"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Fonction pour obtenir le projet actuel
|
||||
get_current_project() {
|
||||
CURRENT_PROJECT=$(gcloud config get-value project 2>/dev/null)
|
||||
if [ -z "$CURRENT_PROJECT" ]; then
|
||||
echo -e "${YELLOW}Aucun projet configuré.${NC}"
|
||||
CURRENT_PROJECT="Aucun"
|
||||
fi
|
||||
}
|
||||
|
||||
# Fonction pour afficher les informations de débogage
|
||||
show_debug_info() {
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Informations de débogage ===${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}1. Version de gcloud:${NC}"
|
||||
gcloud version 2>/dev/null || echo "Erreur lors de la récupération de la version"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}2. Configuration actuelle:${NC}"
|
||||
gcloud config list 2>/dev/null || echo "Erreur lors de la récupération de la configuration"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}3. Comptes authentifiés:${NC}"
|
||||
gcloud auth list 2>/dev/null || echo "Erreur lors de la récupération des comptes"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}4. Test de la commande projects list (brute):${NC}"
|
||||
echo "Commande: gcloud projects list"
|
||||
gcloud projects list 2>&1 || echo "Erreur lors de l'exécution"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}5. Test avec format JSON:${NC}"
|
||||
echo "Commande: gcloud projects list --format=json"
|
||||
gcloud projects list --format="json" 2>&1 | head -20 || echo "Erreur lors de l'exécution"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}6. Permissions et quotas:${NC}"
|
||||
echo "Commande: gcloud projects list --limit=1"
|
||||
gcloud projects list --limit=1 2>&1 || echo "Erreur lors de l'exécution"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Fonction pour gérer l'authentification
|
||||
handle_authentication() {
|
||||
clear
|
||||
echo -e "${RED}${BOLD}=== Erreur d'authentification ===${NC}\n"
|
||||
echo -e "${YELLOW}Aucun projet trouvé ou erreur d'authentification.${NC}\n"
|
||||
|
||||
local auth_options=("Se connecter maintenant (gcloud auth login)" "Réessayer" "← Retour au menu principal")
|
||||
|
||||
echo "Que souhaitez-vous faire ?\n"
|
||||
show_menu "${auth_options[@]}"
|
||||
local choice=$?
|
||||
|
||||
case $choice in
|
||||
0) # Se connecter
|
||||
echo -e "\n${BLUE}Lancement de l'authentification...${NC}"
|
||||
echo -e "${YELLOW}Une page web va s'ouvrir pour vous connecter.${NC}\n"
|
||||
|
||||
if gcloud auth login; then
|
||||
echo -e "\n${GREEN}Authentification réussie!${NC}"
|
||||
sleep 2
|
||||
return 0
|
||||
else
|
||||
echo -e "\n${RED}Échec de l'authentification.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
1) # Réessayer
|
||||
return 0
|
||||
;;
|
||||
2) # Retour
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Fonction pour récupérer la liste des projets
|
||||
get_projects_list() {
|
||||
local projects=()
|
||||
|
||||
# Vérifier si l'utilisateur est connecté
|
||||
local auth_status=$(gcloud auth list --format="value(account)" 2>/dev/null | head -1)
|
||||
if [ -z "$auth_status" ]; then
|
||||
echo -e "${RED}Vous n'êtes pas connecté à GCP.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}Récupération des projets...${NC}"
|
||||
|
||||
# Méthode 1: Utiliser --format=json pour éviter les problèmes de parsing
|
||||
local temp_file=$(mktemp)
|
||||
gcloud projects list --format="json" > "$temp_file" 2>/dev/null
|
||||
|
||||
if [ -s "$temp_file" ]; then
|
||||
# Extraire les IDs de projet du JSON
|
||||
while read -r project_id; do
|
||||
if [ ! -z "$project_id" ]; then
|
||||
projects+=("$project_id")
|
||||
fi
|
||||
done < <(cat "$temp_file" | grep -o '"projectId": "[^"]*"' | cut -d '"' -f 4)
|
||||
else
|
||||
# Méthode 2: Essayer avec la méthode originale
|
||||
gcloud projects list --format="table(projectId)" --sort-by=projectId > "$temp_file" 2>&1
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^[a-zA-Z0-9-]+[[:space:]] ]]; then
|
||||
project_id=$(echo "$line" | awk '{print $1}')
|
||||
if [ ! -z "$project_id" ]; then
|
||||
projects+=("$project_id")
|
||||
fi
|
||||
fi
|
||||
done < <(cat "$temp_file" | tail -n +2)
|
||||
fi
|
||||
|
||||
# Nettoyer le fichier temporaire
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Retourner les projets via une variable globale
|
||||
PROJECTS_LIST=("${projects[@]}")
|
||||
return 0
|
||||
}
|
||||
|
||||
# Fonction pour sélectionner un projet de manière interactive avec filtrage
|
||||
select_project_interactive() {
|
||||
echo -e "\n${BLUE}Récupération de la liste des projets...${NC}"
|
||||
|
||||
# Récupérer tous les projets disponibles
|
||||
local all_projects=()
|
||||
if ! get_projects_list; then
|
||||
if ! handle_authentication; then
|
||||
return 1
|
||||
fi
|
||||
if ! get_projects_list; then
|
||||
echo -e "${RED}Impossible de récupérer la liste des projets.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
all_projects=("${PROJECTS_LIST[@]}")
|
||||
|
||||
if [ ${#all_projects[@]} -eq 0 ]; then
|
||||
echo -e "${RED}Aucun projet accessible trouvé.${NC}"
|
||||
echo -e "${YELLOW}Cela peut être dû à:${NC}"
|
||||
echo -e " • Aucun projet associé à votre compte"
|
||||
echo -e " • Permissions insuffisantes"
|
||||
echo -e " • Problème de configuration gcloud"
|
||||
echo ""
|
||||
|
||||
local retry_options=("Réessayer" "Se reconnecter" "Spécifier manuellement l'ID du projet" "Afficher les détails de débogage" "← Retour au menu principal")
|
||||
echo "Que souhaitez-vous faire ?\n"
|
||||
show_menu "${retry_options[@]}"
|
||||
local retry_choice=$?
|
||||
|
||||
case $retry_choice in
|
||||
0) # Réessayer
|
||||
return select_project_interactive
|
||||
;;
|
||||
1) # Se reconnecter
|
||||
if handle_authentication; then
|
||||
return select_project_interactive
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
2) # Spécifier manuellement l'ID du projet
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Spécifier l'ID du projet ===${NC}\n"
|
||||
echo -e "${YELLOW}Entrez l'ID du projet Firebase/GCP:${NC}"
|
||||
echo -e "(exemple: my-project-12345)\n"
|
||||
read -p "> " manual_project_id
|
||||
|
||||
if [ -z "$manual_project_id" ]; then
|
||||
echo -e "\n${RED}Aucun ID de projet spécifié.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Vérification du projet: $manual_project_id${NC}"
|
||||
if gcloud projects describe "$manual_project_id" &>/dev/null; then
|
||||
echo -e "${GREEN}Projet trouvé!${NC}"
|
||||
all_projects=("$manual_project_id")
|
||||
else
|
||||
echo -e "${RED}Projet non trouvé ou vous n'avez pas les permissions nécessaires.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
3) # Afficher les détails de débogage
|
||||
show_debug_info
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
;;
|
||||
4) # Retour
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Ajouter option de retour
|
||||
all_projects+=("← Retour au menu principal")
|
||||
|
||||
local selected=0
|
||||
local filter=""
|
||||
local filtered_projects=()
|
||||
|
||||
# Fonction pour filtrer les projets
|
||||
filter_projects() {
|
||||
filtered_projects=()
|
||||
for project in "${all_projects[@]}"; do
|
||||
if [[ "$project" == *"$filter"* ]]; then
|
||||
filtered_projects+=("$project")
|
||||
fi
|
||||
done
|
||||
|
||||
# Réajuster la sélection si nécessaire
|
||||
if [ $selected -ge ${#filtered_projects[@]} ]; then
|
||||
selected=0
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialiser le filtre
|
||||
filter_projects
|
||||
|
||||
while true; do
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Sélection du projet ===${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}Projet actuel: ${BOLD}${CURRENT_PROJECT:-"Aucun"}${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Tapez pour filtrer, utilisez ↑↓ pour naviguer, Entrée pour sélectionner:${NC}"
|
||||
echo -e "Filtre: ${BOLD}$filter${NC}"
|
||||
echo ""
|
||||
|
||||
# Afficher les projets filtrés
|
||||
if [ ${#filtered_projects[@]} -eq 0 ]; then
|
||||
echo -e "${RED}Aucun projet trouvé pour le filtre '$filter'${NC}"
|
||||
else
|
||||
for i in "${!filtered_projects[@]}"; do
|
||||
if [ $i -eq $selected ]; then
|
||||
echo -e "${GREEN}► ${filtered_projects[$i]}${NC}"
|
||||
else
|
||||
echo -e " ${filtered_projects[$i]}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Commandes: [Backspace] Effacer filtre | [Esc] Annuler${NC}"
|
||||
|
||||
# Lire une seule touche
|
||||
read -rsn1 key
|
||||
|
||||
case $key in
|
||||
$'\033') # Séquence d'échappement
|
||||
read -rsn2 key
|
||||
case $key in
|
||||
'[A') # Flèche haut
|
||||
if [ ${#filtered_projects[@]} -gt 0 ]; then
|
||||
((selected--))
|
||||
if [ $selected -lt 0 ]; then
|
||||
selected=$((${#filtered_projects[@]}-1))
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
'[B') # Flèche bas
|
||||
if [ ${#filtered_projects[@]} -gt 0 ]; then
|
||||
((selected++))
|
||||
if [ $selected -ge ${#filtered_projects[@]} ]; then
|
||||
selected=0
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
'') # Entrée
|
||||
if [ ${#filtered_projects[@]} -gt 0 ]; then
|
||||
local chosen_project="${filtered_projects[$selected]}"
|
||||
if [[ "$chosen_project" == "← Retour au menu principal" ]]; then
|
||||
return 1
|
||||
else
|
||||
echo -e "\n${BLUE}Configuration du projet: $chosen_project${NC}"
|
||||
if gcloud config set project "$chosen_project" &>/dev/null; then
|
||||
CURRENT_PROJECT="$chosen_project"
|
||||
echo -e "${GREEN}Projet configuré avec succès!${NC}"
|
||||
sleep 2
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Erreur lors de la configuration du projet.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
$'\177'|$'\b') # Backspace
|
||||
if [ ${#filter} -gt 0 ]; then
|
||||
filter="${filter%?}"
|
||||
filter_projects
|
||||
fi
|
||||
;;
|
||||
$'\e') # Escape
|
||||
return 1
|
||||
;;
|
||||
*) # Caractère normal
|
||||
if [[ "$key" =~ [a-zA-Z0-9-] ]]; then
|
||||
filter="$filter$key"
|
||||
filter_projects
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Fonction pour lister et sélectionner un projet (version simplifiée)
|
||||
select_project() {
|
||||
select_project_interactive
|
||||
}
|
||||
|
||||
# Fonction pour afficher les informations de débogage des Cloud Functions
|
||||
show_functions_debug_info() {
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Débogage Cloud Functions ===${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}1. Test Cloud Functions Gen1 (toutes régions):${NC}"
|
||||
gcloud functions list 2>&1 || echo "Erreur lors de la récupération des fonctions Gen1"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}2. Test Cloud Functions Gen2 (Cloud Run):${NC}"
|
||||
gcloud run services list 2>&1 || echo "Erreur lors de la récupération des services Cloud Run"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}3. Régions disponibles pour Cloud Functions:${NC}"
|
||||
gcloud functions regions list 2>&1 || echo "Erreur lors de la récupération des régions"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}4. Configuration actuelle:${NC}"
|
||||
echo "Projet: $(gcloud config get-value project 2>/dev/null)"
|
||||
echo "Région par défaut: $(gcloud config get-value functions/region 2>/dev/null || echo 'Non définie')"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}5. APIs activées:${NC}"
|
||||
gcloud services list --enabled --filter="name:cloudfunctions OR name:run OR name:cloudbuild" 2>&1 || echo "Erreur lors de la vérification des APIs"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Fonction pour sélectionner une région de manière interactive avec filtrage
|
||||
select_region_interactive() {
|
||||
echo -e "\n${BLUE}Récupération des régions disponibles...${NC}"
|
||||
|
||||
# Récupérer toutes les régions disponibles
|
||||
local all_regions=()
|
||||
while IFS= read -r region; do
|
||||
if [[ -n "$region" ]]; then
|
||||
all_regions+=("$region")
|
||||
fi
|
||||
done < <(gcloud functions regions list --format="value(name)" 2>/dev/null | sort)
|
||||
|
||||
if [ ${#all_regions[@]} -eq 0 ]; then
|
||||
echo -e "${RED}Impossible de récupérer la liste des régions.${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ajouter option de retour
|
||||
all_regions+=("← Retour au menu précédent")
|
||||
|
||||
local selected=0
|
||||
local filter=""
|
||||
local filtered_regions=()
|
||||
|
||||
# Fonction pour filtrer les régions
|
||||
filter_regions() {
|
||||
filtered_regions=()
|
||||
for region in "${all_regions[@]}"; do
|
||||
if [[ "$region" == *"$filter"* ]]; then
|
||||
filtered_regions+=("$region")
|
||||
fi
|
||||
done
|
||||
|
||||
# Réajuster la sélection si nécessaire
|
||||
if [ $selected -ge ${#filtered_regions[@]} ]; then
|
||||
selected=0
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialiser le filtre
|
||||
filter_regions
|
||||
|
||||
while true; do
|
||||
clear
|
||||
echo -e "${BLUE}${BOLD}=== Sélection de région ===${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}Configuration actuelle:${NC}"
|
||||
echo "Région par défaut: $(gcloud config get-value functions/region 2>/dev/null || echo 'Non définie')"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Tapez pour filtrer, utilisez ↑↓ pour naviguer, Entrée pour sélectionner:${NC}"
|
||||
echo -e "Filtre: ${BOLD}$filter${NC}"
|
||||
echo ""
|
||||
|
||||
# Afficher les régions filtrées
|
||||
if [ ${#filtered_regions[@]} -eq 0 ]; then
|
||||
echo -e "${RED}Aucune région trouvée pour le filtre '$filter'${NC}"
|
||||
else
|
||||
for i in "${!filtered_regions[@]}"; do
|
||||
if [ $i -eq $selected ]; then
|
||||
echo -e "${GREEN}► ${filtered_regions[$i]}${NC}"
|
||||
else
|
||||
echo -e " ${filtered_regions[$i]}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Commandes: [Backspace] Effacer filtre | [Esc] Annuler${NC}"
|
||||
|
||||
# Lire une seule touche
|
||||
read -rsn1 key
|
||||
|
||||
case $key in
|
||||
$'\033') # Séquence d'échappement
|
||||
read -rsn2 key
|
||||
case $key in
|
||||
'[A') # Flèche haut
|
||||
if [ ${#filtered_regions[@]} -gt 0 ]; then
|
||||
((selected--))
|
||||
if [ $selected -lt 0 ]; then
|
||||
selected=$((${#filtered_regions[@]}-1))
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
'[B') # Flèche bas
|
||||
if [ ${#filtered_regions[@]} -gt 0 ]; then
|
||||
((selected++))
|
||||
if [ $selected -ge ${#filtered_regions[@]} ]; then
|
||||
selected=0
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
'') # Entrée
|
||||
if [ ${#filtered_regions[@]} -gt 0 ]; then
|
||||
local chosen_region="${filtered_regions[$selected]}"
|
||||
if [[ "$chosen_region" == "← Retour au menu précédent" ]]; then
|
||||
return 1
|
||||
else
|
||||
echo -e "\n${BLUE}Configuration de la région $chosen_region...${NC}"
|
||||
if gcloud config set functions/region "$chosen_region" 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ Région configurée: $chosen_region${NC}"
|
||||
echo -e "\n${BLUE}Nouvelle tentative de récupération des fonctions...${NC}"
|
||||
sleep 2
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ Erreur lors de la configuration de la région${NC}"
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
$'\177'|$'\b') # Backspace
|
||||
if [ ${#filter} -gt 0 ]; then
|
||||
filter="${filter%?}"
|
||||
filter_regions
|
||||
fi
|
||||
;;
|
||||
$'\e') # Escape
|
||||
return 1
|
||||
;;
|
||||
*) # Caractère normal
|
||||
if [[ "$key" =~ [a-zA-Z0-9-] ]]; then
|
||||
filter="$filter$key"
|
||||
filter_regions
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Fonction pour spécifier une région et réessayer (version simplifiée)
|
||||
specify_region_and_retry() {
|
||||
if select_region_interactive; then
|
||||
delete_all_functions
|
||||
fi
|
||||
}
|
||||
|
||||
# Fonction pour supprimer toutes les Cloud Functions
|
||||
delete_all_functions() {
|
||||
# Ne pas clear ici pour garder les informations du menu principal visible
|
||||
echo -e "${BLUE}Récupération des Cloud Functions du projet $CURRENT_PROJECT...${NC}"
|
||||
|
||||
# Obtenir toutes les fonctions (sans distinction Gen1/Gen2)
|
||||
local all_functions=()
|
||||
local function_names=()
|
||||
local function_regions=()
|
||||
|
||||
echo -e "\n${YELLOW}Recherche de toutes les Cloud Functions...${NC}"
|
||||
|
||||
# Récupérer tous les noms de fonctions avec leurs régions
|
||||
while read -r line; do
|
||||
if [[ -n "$line" && "$line" != *"NAME"* ]]; then
|
||||
# Extraire le nom et la région de la fonction
|
||||
name=$(echo "$line" | awk '{print $1}')
|
||||
region=$(echo "$line" | awk '{print $4}')
|
||||
|
||||
if [[ -n "$name" && "$name" != "NAME" && -n "$region" ]]; then
|
||||
function_names+=("$name")
|
||||
function_regions+=("$region")
|
||||
all_functions+=("$name ($region)")
|
||||
fi
|
||||
fi
|
||||
done < <(gcloud functions list --format="table(name,state,trigger,region)" 2>/dev/null | tail -n +2)
|
||||
|
||||
echo -e "${GREEN}Total des fonctions trouvées: ${#function_names[@]}${NC}\n"
|
||||
|
||||
if [ ${#function_names[@]} -eq 0 ]; then
|
||||
echo -e "${YELLOW}Aucune Cloud Function trouvée dans le projet $CURRENT_PROJECT.${NC}"
|
||||
echo -e "${YELLOW}Cela peut être dû à:${NC}"
|
||||
echo -e " • Aucune fonction déployée"
|
||||
echo -e " • Fonctions dans une région non configurée"
|
||||
echo -e " • APIs Cloud Functions/Cloud Run non activées"
|
||||
echo -e " • Permissions insuffisantes"
|
||||
echo ""
|
||||
|
||||
local debug_options=("Afficher les informations de débogage" "Spécifier une région" "← Retour au menu principal")
|
||||
echo "Que souhaitez-vous faire ?\n"
|
||||
show_menu "${debug_options[@]}"
|
||||
local debug_choice=$?
|
||||
|
||||
case $debug_choice in
|
||||
0) # Afficher debug
|
||||
show_functions_debug_info
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
;;
|
||||
1) # Spécifier région
|
||||
specify_region_and_retry
|
||||
;;
|
||||
2) # Retour
|
||||
return
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\n${YELLOW}${BOLD}Fonctions trouvées (${#function_names[@]}):${NC}"
|
||||
for func in "${function_names[@]}"; do
|
||||
echo -e " • $func"
|
||||
done
|
||||
|
||||
echo -e "\n${RED}${BOLD}⚠️ ATTENTION: Cette action supprimera TOUTES les Cloud Functions du projet!${NC}"
|
||||
echo -e "${RED}Cette action est IRRÉVERSIBLE!${NC}\n"
|
||||
|
||||
local confirm_options=("Oui, supprimer toutes les fonctions" "Non, annuler")
|
||||
show_menu "${confirm_options[@]}"
|
||||
local confirm=$?
|
||||
|
||||
if [ $confirm -eq 0 ]; then
|
||||
echo -e "\n${RED}Suppression en cours...${NC}\n"
|
||||
|
||||
local success_count=0
|
||||
local error_count=0
|
||||
|
||||
# Supprimer toutes les fonctions avec leur région spécifique
|
||||
for i in "${!function_names[@]}"; do
|
||||
local func="${function_names[$i]}"
|
||||
local region="${function_regions[$i]}"
|
||||
|
||||
echo -e "Suppression de: ${BOLD}$func${NC} (région: $region)"
|
||||
|
||||
# Capturer l'erreur détaillée avec la région spécifique
|
||||
local error_output
|
||||
error_output=$(gcloud functions delete "$func" --region="$region" --quiet 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ $func supprimée${NC}"
|
||||
((success_count++))
|
||||
else
|
||||
echo -e "${RED}✗ Erreur lors de la suppression de $func${NC}"
|
||||
echo -e "${RED} Détail: $error_output${NC}"
|
||||
((error_count++))
|
||||
fi
|
||||
|
||||
# Petite pause pour éviter les erreurs de rate limiting
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
echo -e "\n${BOLD}Résumé:${NC}"
|
||||
echo -e "${GREEN}• Fonctions supprimées: $success_count${NC}"
|
||||
if [ $error_count -gt 0 ]; then
|
||||
echo -e "${RED}• Erreurs: $error_count${NC}"
|
||||
echo -e "${YELLOW}Il y a eu des erreurs lors de la suppression.${NC}"
|
||||
echo -e "${YELLOW}Vérifiez les permissions et l'état des fonctions.${NC}"
|
||||
echo -e "${YELLOW}Les erreurs détaillées sont affichées ci-dessus.${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Toutes les fonctions ont été supprimées avec succès!${NC}"
|
||||
fi
|
||||
|
||||
echo -e "\n${BLUE}Appuyez sur Entrée pour continuer...${NC}"
|
||||
read -r
|
||||
else
|
||||
echo -e "\n${YELLOW}Suppression annulée.${NC}"
|
||||
echo -e "\n${BLUE}Appuyez sur Entrée pour continuer...${NC}"
|
||||
read -r
|
||||
fi
|
||||
|
||||
read -p "Appuyez sur Entrée pour continuer..."
|
||||
}
|
||||
|
||||
# Fonction principale
|
||||
main() {
|
||||
clear
|
||||
check_gcloud
|
||||
|
||||
while true; do
|
||||
get_current_project
|
||||
|
||||
local main_options=()
|
||||
if [ "$CURRENT_PROJECT" != "Aucun" ]; then
|
||||
main_options=("Supprimer toutes les Cloud Functions" "Changer de projet" "Se connecter à GCP" "Quitter")
|
||||
else
|
||||
main_options=("Changer de projet" "Se connecter à GCP" "Quitter")
|
||||
fi
|
||||
|
||||
show_menu "${main_options[@]}"
|
||||
local choice=$?
|
||||
|
||||
if [ "$CURRENT_PROJECT" != "Aucun" ]; then
|
||||
case $choice in
|
||||
0) # Supprimer toutes les fonctions
|
||||
delete_all_functions
|
||||
;;
|
||||
1) # Changer de projet
|
||||
select_project
|
||||
;;
|
||||
2) # Se connecter à GCP
|
||||
handle_authentication
|
||||
;;
|
||||
3) # Quitter
|
||||
echo -e "\n${YELLOW}Au revoir!${NC}"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
case $choice in
|
||||
0) # Changer de projet
|
||||
select_project
|
||||
;;
|
||||
1) # Se connecter à GCP
|
||||
handle_authentication
|
||||
;;
|
||||
2) # Quitter
|
||||
echo -e "\n${YELLOW}Au revoir!${NC}"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Gestion des signaux pour un arrêt propre
|
||||
trap 'echo -e "\n${YELLOW}Script interrompu.${NC}"; exit 0' INT TERM
|
||||
|
||||
# Lancement du script
|
||||
main
|
||||
@@ -0,0 +1,44 @@
|
||||
function basicTemplate({ title = "", content = "", button = null }) {
|
||||
const btn = button?.url
|
||||
? `<p><a href="${button.url}" style="display:inline-block;padding:10px 16px;background:#6C5CE7;color:#fff;border-radius:8px;text-decoration:none">${
|
||||
button?.label || "Ouvrir"
|
||||
}</a></p>`
|
||||
: "";
|
||||
return `<!doctype html><html><body style="font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;line-height:1.5;color:#111">
|
||||
<div style="max-width:640px;margin:40px auto;padding:24px;border:1px solid #eee;border-radius:12px">
|
||||
<h2 style="margin:0 0 16px">${title}</h2>
|
||||
<div>${content}</div>
|
||||
${btn}
|
||||
<hr style="margin:24px 0;border:none;border-top:1px solid #eee" />
|
||||
<p style="color:#666;font-size:12px">minuit.app</p>
|
||||
</div>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function welcomeTemplate({ firstName = "", lastName = "" }) {
|
||||
const fullName = [firstName, lastName].filter(Boolean).join(" ").trim();
|
||||
const greeting = fullName ? `Salut ${fullName},` : "Salut,";
|
||||
return `<!doctype html><html><body style="margin:0;padding:0;background:#0b0b10;color:#fff;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif">
|
||||
<div style="max-width:640px;margin:0 auto;padding:40px 24px">
|
||||
<div style="background:#151520;border-radius:16px;overflow:hidden;border:1px solid rgba(255,255,255,0.08)">
|
||||
<div style="padding:32px 28px">
|
||||
<p style="margin:0 0 12px;font-size:16px;letter-spacing:0.2px">${greeting}</p>
|
||||
<h1 style="margin:0 0 16px;font-size:28px;line-height:1.2">Bienvenue sur MusicLand</h1>
|
||||
<p style="margin:0 0 16px;font-size:16px;line-height:1.6;color:rgba(255,255,255,0.85)">
|
||||
Nous sommes ravis de t'accueillir dans la communauté des artistes et passionnés de MusicLand.
|
||||
</p>
|
||||
<p style="margin:0 0 16px;font-size:15px;line-height:1.6;color:rgba(255,255,255,0.75)">
|
||||
Explore les playlists collaboratives, dépose tes projets et découvre ce que prépare la scène émergente.
|
||||
</p>
|
||||
<p style="margin:0;font-size:15px;line-height:1.6;color:rgba(255,255,255,0.75)">
|
||||
L'équipe MusicLand
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin:32px 0 0;text-align:center;font-size:12px;color:rgba(255,255,255,0.45)">minuit.app · Paris, France</p>
|
||||
</div>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
exports.basicTemplate = basicTemplate;
|
||||
exports.welcomeTemplate = welcomeTemplate;
|
||||
Generated
+382
-274
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@
|
||||
"firebase-functions": "^6.4.0",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"genkit": "^1.18.0",
|
||||
"resend": "^6.3.0",
|
||||
"sharp": "^0.33.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,11 +5,18 @@ const {
|
||||
const admin = require("firebase-admin");
|
||||
const { refList, ALERT_TYPE } = require("../index");
|
||||
const { Expo } = require("expo-server-sdk");
|
||||
const { Resend } = require("resend");
|
||||
const { basicTemplate } = require("../helpers/email");
|
||||
const { RESEND_API_KEY } = require("../config/keys");
|
||||
|
||||
const resendInstance = new Resend(RESEND_API_KEY);
|
||||
|
||||
// Initialisation de Expo SDK
|
||||
let expo = new Expo();
|
||||
const db = admin.firestore();
|
||||
|
||||
const EMAIL_FROM = "MusicLand <musicland@minuit.app>";
|
||||
|
||||
exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
|
||||
{ region: "europe-west1", document: "notifications/{notificationId}" },
|
||||
async (event) => {
|
||||
@@ -26,14 +33,18 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
|
||||
throw new Error("Receiver and message are required");
|
||||
}
|
||||
|
||||
const userSnap = await db.collection(receiverCollection).doc(receiver).get();
|
||||
const { pushToken = null, pushTokens = [] } = userSnap?.data() || {};
|
||||
const {
|
||||
pushToken = null,
|
||||
pushTokens = [],
|
||||
email = "",
|
||||
emailNotifications = false,
|
||||
} = (await db.collection(receiverCollection).doc(receiver).get()).data();
|
||||
|
||||
const tokensSet = new Set(
|
||||
[]
|
||||
.concat(Array.isArray(pushTokens) ? pushTokens : [])
|
||||
.concat(pushToken ? [pushToken] : [])
|
||||
.filter(Boolean)
|
||||
.filter(Boolean),
|
||||
);
|
||||
const tokens = Array.from(tokensSet);
|
||||
|
||||
@@ -50,11 +61,31 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
|
||||
message: message,
|
||||
data: notifData || {},
|
||||
});
|
||||
if (emailNotifications && !!email) {
|
||||
try {
|
||||
if (!email) {
|
||||
throw new Error("userMail is required");
|
||||
}
|
||||
if (!email?.subject || !email?.html) {
|
||||
throw new Error("Email subject and html are required");
|
||||
}
|
||||
await resendInstance.emails.send({
|
||||
from: EMAIL_FROM,
|
||||
to: [email],
|
||||
subject: title,
|
||||
html: basicTemplate({ title, content: message }),
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error sending email:", e);
|
||||
}
|
||||
} else {
|
||||
console.log(`Email disabled for user ${receiver} and type ${type}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Fonction pour envoyer la notification via Expo SDK
|
||||
@@ -144,7 +175,11 @@ async function sendExpoNotification({
|
||||
}
|
||||
}
|
||||
|
||||
async function removeInvalidTokens({ tokens = [], receiverId, receiverCollection }) {
|
||||
async function removeInvalidTokens({
|
||||
tokens = [],
|
||||
receiverId,
|
||||
receiverCollection,
|
||||
}) {
|
||||
try {
|
||||
if (!receiverId || !tokens.length) {
|
||||
return;
|
||||
@@ -170,7 +205,7 @@ async function removeInvalidTokens({ tokens = [], receiverId, receiverCollection
|
||||
await docRef.set(updates, { merge: true });
|
||||
console.log(
|
||||
"Pruned invalid push tokens",
|
||||
JSON.stringify({ receiverId, tokens: uniqueTokens }, null, 2)
|
||||
JSON.stringify({ receiverId, tokens: uniqueTokens }, null, 2),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Failed to prune invalid push tokens:", error);
|
||||
@@ -204,7 +239,7 @@ const sendNotification = async ({
|
||||
const { id } = await refList.notifications.add(payload);
|
||||
console.log(
|
||||
"[sendNotification] Notification created",
|
||||
JSON.stringify({ id, receiver, receiverCollection }, null, 2)
|
||||
JSON.stringify({ id, receiver, receiverCollection }, null, 2),
|
||||
);
|
||||
|
||||
return id;
|
||||
@@ -224,7 +259,7 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
try {
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Trigger received",
|
||||
JSON.stringify(event.params || {}, null, 2)
|
||||
JSON.stringify(event.params || {}, null, 2),
|
||||
);
|
||||
const { data: snap } = event;
|
||||
const { projectId, commentId } = event.params || {};
|
||||
@@ -233,21 +268,21 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
if (!projectId || !comment) {
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Missing project/comment data",
|
||||
{ hasProjectId: !!projectId, hasComment: !!comment }
|
||||
{ hasProjectId: !!projectId, hasComment: !!comment },
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Comment payload",
|
||||
JSON.stringify(comment, null, 2)
|
||||
JSON.stringify(comment, null, 2),
|
||||
);
|
||||
|
||||
const projectSnap = await refList.projects.doc(projectId).get();
|
||||
if (!projectSnap.exists) {
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Project not found",
|
||||
projectId
|
||||
projectId,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -258,7 +293,7 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
if (!receiver || receiver === comment.userId) {
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Invalid receiver",
|
||||
JSON.stringify({ receiver, commentUserId: comment.userId })
|
||||
JSON.stringify({ receiver, commentUserId: comment.userId }),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -283,8 +318,8 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
projectId,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
await sendNotification({
|
||||
@@ -308,7 +343,7 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
});
|
||||
|
||||
console.log(
|
||||
"[createProjectCommentNotification] Notification creation complete"
|
||||
"[createProjectCommentNotification] Notification creation complete",
|
||||
);
|
||||
|
||||
return null;
|
||||
@@ -316,7 +351,7 @@ exports.createProjectCommentNotification = onDocumentCreated(
|
||||
console.log("createProjectCommentNotification error:", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
exports.createProjectLikeNotification = onDocumentWritten(
|
||||
@@ -356,10 +391,10 @@ exports.createProjectLikeNotification = onDocumentWritten(
|
||||
const beforePlaybackSet = new Set(beforePlaybackLikes);
|
||||
|
||||
const newSongLikers = afterSongLikes.filter(
|
||||
(uid) => uid && !beforeSongSet.has(uid)
|
||||
(uid) => uid && !beforeSongSet.has(uid),
|
||||
);
|
||||
const newPlaybackLikers = afterPlaybackLikes.filter(
|
||||
(uid) => uid && !beforePlaybackSet.has(uid)
|
||||
(uid) => uid && !beforePlaybackSet.has(uid),
|
||||
);
|
||||
|
||||
const newLikers = [];
|
||||
@@ -413,7 +448,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
|
||||
});
|
||||
|
||||
return null;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
return null;
|
||||
@@ -421,7 +456,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
|
||||
console.log("[createProjectLikeNotification] error:", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
exports.createNewFollowerNotification = onDocumentWritten(
|
||||
@@ -451,7 +486,9 @@ exports.createNewFollowerNotification = onDocumentWritten(
|
||||
}
|
||||
|
||||
const previousSet = new Set(beforeFollowers);
|
||||
const newFollowers = afterFollowers.filter((uid) => !previousSet.has(uid));
|
||||
const newFollowers = afterFollowers.filter(
|
||||
(uid) => !previousSet.has(uid),
|
||||
);
|
||||
|
||||
if (!newFollowers.length) {
|
||||
return null;
|
||||
@@ -487,7 +524,7 @@ exports.createNewFollowerNotification = onDocumentWritten(
|
||||
});
|
||||
|
||||
return null;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
return null;
|
||||
@@ -495,5 +532,5 @@ exports.createNewFollowerNotification = onDocumentWritten(
|
||||
console.log("[createNewFollowerNotification] error:", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
+38
-1
@@ -1,7 +1,44 @@
|
||||
const admin = require("firebase-admin");
|
||||
const { onDocumentDeleted } = require("firebase-functions/firestore");
|
||||
const {
|
||||
onDocumentDeleted,
|
||||
onDocumentCreated,
|
||||
} = require("firebase-functions/firestore");
|
||||
const { refList } = require("../index");
|
||||
const { deleteFolder } = require("../helpers/firebase");
|
||||
const { Resend } = require("resend");
|
||||
const { welcomeTemplate } = require("../helpers/email");
|
||||
const { RESEND_API_KEY } = require("../config/keys");
|
||||
|
||||
const resendClient = new Resend(RESEND_API_KEY);
|
||||
const WELCOME_EMAIL_FROM =
|
||||
process.env.RESEND_FROM_EMAIL || "MusicLand <musicland@minuit.app>";
|
||||
const WELCOME_EMAIL_SUBJECT = "Bienvenue sur MusicLand";
|
||||
|
||||
exports.onUserCreated = onDocumentCreated("users/{userID}", async (event) => {
|
||||
try {
|
||||
const { email = "", firstName = "", lastName = "" } = event?.data?.data();
|
||||
if (email) {
|
||||
if (!resendClient) {
|
||||
console.warn("Resend API key not configured; skipping welcome email.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await resendClient.emails.send({
|
||||
from: WELCOME_EMAIL_FROM,
|
||||
to: [email],
|
||||
subject: WELCOME_EMAIL_SUBJECT,
|
||||
html: welcomeTemplate({ firstName, lastName }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to send welcome email:", error);
|
||||
}
|
||||
} else {
|
||||
throw new Error("User created with empty email");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
exports.onUserDelete = onDocumentDeleted("users/{userID}", async (event) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user