Files
spotizerr-dev/routes/credentials.py
2025-06-07 18:47:18 +02:00

242 lines
9.5 KiB
Python
Executable File

from flask import Blueprint, request, jsonify
from routes.utils.credentials import (
get_credential,
list_credentials,
create_credential,
delete_credential,
edit_credential,
init_credentials_db,
# Import new utility functions for global Spotify API creds
_get_global_spotify_api_creds,
save_global_spotify_api_creds,
)
import logging
logger = logging.getLogger(__name__)
credentials_bp = Blueprint("credentials", __name__)
# Initialize the database and tables when the blueprint is loaded
init_credentials_db()
@credentials_bp.route("/spotify_api_config", methods=["GET", "PUT"])
def handle_spotify_api_config():
"""Handles GET and PUT requests for the global Spotify API client_id and client_secret."""
try:
if request.method == "GET":
client_id, client_secret = _get_global_spotify_api_creds()
if client_id is not None and client_secret is not None:
return jsonify(
{"client_id": client_id, "client_secret": client_secret}
), 200
else:
# If search.json exists but is empty/incomplete, or doesn't exist
return jsonify(
{
"warning": "Global Spotify API credentials are not fully configured or file is missing.",
"client_id": client_id or "",
"client_secret": client_secret or "",
}
), 200
elif request.method == "PUT":
data = request.get_json()
if not data or "client_id" not in data or "client_secret" not in data:
return jsonify(
{
"error": "Request body must contain 'client_id' and 'client_secret'"
}
), 400
client_id = data["client_id"]
client_secret = data["client_secret"]
if not isinstance(client_id, str) or not isinstance(client_secret, str):
return jsonify(
{"error": "'client_id' and 'client_secret' must be strings"}
), 400
if save_global_spotify_api_creds(client_id, client_secret):
return jsonify(
{"message": "Global Spotify API credentials updated successfully."}
), 200
else:
return jsonify(
{"error": "Failed to save global Spotify API credentials."}
), 500
except Exception as e:
logger.error(f"Error in /spotify_api_config: {e}", exc_info=True)
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
@credentials_bp.route("/<service>", methods=["GET"])
def handle_list_credentials(service):
try:
if service not in ["spotify", "deezer"]:
return jsonify(
{"error": "Invalid service. Must be 'spotify' or 'deezer'"}
), 400
return jsonify(list_credentials(service))
except ValueError as e: # Should not happen with service check above
return jsonify({"error": str(e)}), 400
except Exception as e:
logger.error(f"Error listing credentials for {service}: {e}", exc_info=True)
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
@credentials_bp.route("/<service>/<name>", methods=["GET", "POST", "PUT", "DELETE"])
def handle_single_credential(service, name):
try:
if service not in ["spotify", "deezer"]:
return jsonify(
{"error": "Invalid service. Must be 'spotify' or 'deezer'"}
), 400
# cred_type logic is removed for Spotify as API keys are global.
# For Deezer, it's always 'credentials' type implicitly.
if request.method == "GET":
# get_credential for Spotify now only returns region and blob_file_path
return jsonify(get_credential(service, name))
elif request.method == "POST":
data = request.get_json()
if not data:
return jsonify({"error": "Request body cannot be empty."}), 400
# create_credential for Spotify now expects 'region' and 'blob_content'
# For Deezer, it expects 'arl' and 'region'
# Validation is handled within create_credential utility function
result = create_credential(service, name, data)
return jsonify(
{
"message": f"Credential for '{name}' ({service}) created successfully.",
"details": result,
}
), 201
elif request.method == "PUT":
data = request.get_json()
if not data:
return jsonify({"error": "Request body cannot be empty."}), 400
# edit_credential for Spotify now handles updates to 'region', 'blob_content'
# For Deezer, 'arl', 'region'
result = edit_credential(service, name, data)
return jsonify(
{
"message": f"Credential for '{name}' ({service}) updated successfully.",
"details": result,
}
)
elif request.method == "DELETE":
# delete_credential for Spotify also handles deleting the blob directory
result = delete_credential(service, name)
return jsonify(
{
"message": f"Credential for '{name}' ({service}) deleted successfully.",
"details": result,
}
)
except (ValueError, FileNotFoundError, FileExistsError) as e:
status_code = 400
if isinstance(e, FileNotFoundError):
status_code = 404
elif isinstance(e, FileExistsError):
status_code = 409
logger.warning(f"Client error in /<{service}>/<{name}>: {str(e)}")
return jsonify({"error": str(e)}), status_code
except Exception as e:
logger.error(f"Server error in /<{service}>/<{name}>: {e}", exc_info=True)
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
# The '/search/<service>/<name>' route is now obsolete for Spotify and has been removed.
@credentials_bp.route("/all/<service>", methods=["GET"])
def handle_all_credentials(service):
"""Lists all credentials for a given service. For Spotify, API keys are global and not listed per account."""
try:
if service not in ["spotify", "deezer"]:
return jsonify(
{"error": "Invalid service. Must be 'spotify' or 'deezer'"}
), 400
credentials_list = []
account_names = list_credentials(service) # This lists names from DB
for name in account_names:
try:
# get_credential for Spotify returns region and blob_file_path.
# For Deezer, it returns arl and region.
account_data = get_credential(service, name)
# We don't add global Spotify API keys here as they are separate
credentials_list.append({"name": name, "details": account_data})
except FileNotFoundError:
logger.warning(
f"Credential name '{name}' listed for service '{service}' but not found by get_credential. Skipping."
)
except Exception as e_inner:
logger.error(
f"Error fetching details for credential '{name}' ({service}): {e_inner}",
exc_info=True,
)
credentials_list.append(
{
"name": name,
"error": f"Could not retrieve details: {str(e_inner)}",
}
)
return jsonify(credentials_list)
except Exception as e:
logger.error(f"Error in /all/{service}: {e}", exc_info=True)
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500
@credentials_bp.route("/markets", methods=["GET"])
def handle_markets():
"""
Returns a list of unique market regions for Deezer and Spotify accounts.
"""
try:
deezer_regions = set()
spotify_regions = set()
# Process Deezer accounts
deezer_account_names = list_credentials("deezer")
for name in deezer_account_names:
try:
account_data = get_credential("deezer", name)
if account_data and "region" in account_data and account_data["region"]:
deezer_regions.add(account_data["region"])
except Exception as e:
logger.warning(
f"Could not retrieve region for deezer account {name}: {e}"
)
# Process Spotify accounts
spotify_account_names = list_credentials("spotify")
for name in spotify_account_names:
try:
account_data = get_credential("spotify", name)
if account_data and "region" in account_data and account_data["region"]:
spotify_regions.add(account_data["region"])
except Exception as e:
logger.warning(
f"Could not retrieve region for spotify account {name}: {e}"
)
return jsonify(
{
"deezer": sorted(list(deezer_regions)),
"spotify": sorted(list(spotify_regions)),
}
), 200
except Exception as e:
logger.error(f"Error in /markets: {e}", exc_info=True)
return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500