larxius commited on
Commit
a1d596a
·
verified ·
1 Parent(s): a566bcf

Update backend/utils/firebase_storage.py

Browse files
Files changed (1) hide show
  1. backend/utils/firebase_storage.py +50 -43
backend/utils/firebase_storage.py CHANGED
@@ -21,14 +21,15 @@ from typing import Optional, Union, Any
21
  logger = logging.getLogger(__name__)
22
 
23
  # Firebase references — lazily initialized
24
- _app = None
25
  _bucket: Any = None
26
- _initialized = False
27
 
28
 
29
  def _get_credentials_path() -> Optional[str]:
30
  """Retrieve Firebase credentials path/string from environment."""
31
- return os.getenv("FIREBASE_CREDENTIALS")
 
32
 
33
 
34
  def _get_bucket_name() -> Optional[str]:
@@ -52,8 +53,8 @@ def init_firebase(force_reinit: bool = False) -> bool:
52
  """
53
  global _app, _bucket, _initialized
54
 
55
- if _initialized and not force_reinit:
56
- return _bucket is not None
57
 
58
  creds_path = _get_credentials_path()
59
  bucket_name = _get_bucket_name()
@@ -70,27 +71,38 @@ def init_firebase(force_reinit: bool = False) -> bool:
70
  import firebase_admin
71
  from firebase_admin import credentials, storage
72
 
73
- if not firebase_admin._apps:
74
- # Support file path, base64-encoded JSON, and direct JSON string
75
- cred = None
76
- if os.path.isfile(creds_path):
77
- cred = credentials.Certificate(creds_path)
78
- else:
79
- try:
80
- # Attempt base64 decode
81
- creds_json = base64.b64decode(creds_path).decode("utf-8")
82
- creds_dict = json.loads(creds_json)
83
- except Exception:
84
- # Fallback to direct JSON string
85
- creds_dict = json.loads(creds_path)
 
 
86
  cred = credentials.Certificate(creds_dict)
87
 
 
 
88
  _app = firebase_admin.initialize_app(cred, {"storageBucket": bucket_name})
89
  else:
90
- _app = firebase_admin.get_app()
 
 
 
91
 
92
  # Always bind to the specific bucket name requested
93
- _bucket = storage.bucket(name=bucket_name)
 
 
 
 
94
  _initialized = True
95
  logger.info(f"[Firebase] Initialized successfully. Bucket: {bucket_name}")
96
  return True
@@ -125,11 +137,26 @@ def is_available() -> bool:
125
 
126
  def _build_fallback_url(destination_blob: str) -> str:
127
  """Generate public media download URL for Uniform Bucket-Level Access buckets."""
128
- encoded_blob = urllib.parse.quote(destination_blob, safe='')
 
129
  bucket_name = _bucket.name if _bucket and hasattr(_bucket, 'name') else _get_bucket_name() or "storage"
130
  return f"https://firebasestorage.googleapis.com/v0/b/{bucket_name}/o/{encoded_blob}?alt=media"
131
 
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  def upload_bytes(
134
  data: bytes,
135
  content_type: str,
@@ -157,17 +184,7 @@ def upload_bytes(
157
  try:
158
  blob = _bucket.blob(destination_blob)
159
  blob.upload_from_string(data, content_type=content_type)
160
-
161
- url = None
162
- try:
163
- blob.make_public()
164
- url = getattr(blob, 'public_url', None)
165
- except Exception as pub_err:
166
- logger.debug(f"[Firebase] make_public skipped (Uniform Bucket Access active): {pub_err}")
167
-
168
- if not url:
169
- url = _build_fallback_url(destination_blob)
170
-
171
  logger.info(f"[Firebase] Uploaded {destination_blob} ({len(data)} bytes)")
172
  return url
173
 
@@ -203,17 +220,7 @@ def upload_fileobj(
203
  pass
204
 
205
  blob.upload_from_file(fileobj, content_type=content_type)
206
-
207
- url = None
208
- try:
209
- blob.make_public()
210
- url = getattr(blob, 'public_url', None)
211
- except Exception as pub_err:
212
- logger.debug(f"[Firebase] make_public skipped (Uniform Bucket Access active): {pub_err}")
213
-
214
- if not url:
215
- url = _build_fallback_url(destination_blob)
216
-
217
  logger.info(f"[Firebase] Uploaded fileobj to {destination_blob}")
218
  return url
219
 
 
21
  logger = logging.getLogger(__name__)
22
 
23
  # Firebase references — lazily initialized
24
+ _app: Any = None
25
  _bucket: Any = None
26
+ _initialized: bool = False
27
 
28
 
29
  def _get_credentials_path() -> Optional[str]:
30
  """Retrieve Firebase credentials path/string from environment."""
31
+ creds = os.getenv("FIREBASE_CREDENTIALS")
32
+ return creds.strip() if creds else None
33
 
34
 
35
  def _get_bucket_name() -> Optional[str]:
 
53
  """
54
  global _app, _bucket, _initialized
55
 
56
+ if _initialized and not force_reinit and _bucket is not None:
57
+ return True
58
 
59
  creds_path = _get_credentials_path()
60
  bucket_name = _get_bucket_name()
 
71
  import firebase_admin
72
  from firebase_admin import credentials, storage
73
 
74
+ # Parse credentials from file, raw JSON string, or base64 JSON
75
+ cred = None
76
+ if os.path.isfile(creds_path):
77
+ cred = credentials.Certificate(creds_path)
78
+ elif creds_path.startswith('{'):
79
+ cred = credentials.Certificate(json.loads(creds_path))
80
+ else:
81
+ try:
82
+ # Attempt base64 decode
83
+ creds_json = base64.b64decode(creds_path).decode("utf-8")
84
+ creds_dict = json.loads(creds_json)
85
+ cred = credentials.Certificate(creds_dict)
86
+ except Exception:
87
+ # Fallback to raw JSON load
88
+ creds_dict = json.loads(creds_path)
89
  cred = credentials.Certificate(creds_dict)
90
 
91
+ # Initialize or retrieve Firebase Admin app instance
92
+ if not firebase_admin._apps:
93
  _app = firebase_admin.initialize_app(cred, {"storageBucket": bucket_name})
94
  else:
95
+ try:
96
+ _app = firebase_admin.get_app()
97
+ except ValueError:
98
+ _app = firebase_admin.initialize_app(cred, {"storageBucket": bucket_name})
99
 
100
  # Always bind to the specific bucket name requested
101
+ if _app:
102
+ _bucket = storage.bucket(name=bucket_name, app=_app)
103
+ else:
104
+ _bucket = storage.bucket(name=bucket_name)
105
+
106
  _initialized = True
107
  logger.info(f"[Firebase] Initialized successfully. Bucket: {bucket_name}")
108
  return True
 
137
 
138
  def _build_fallback_url(destination_blob: str) -> str:
139
  """Generate public media download URL for Uniform Bucket-Level Access buckets."""
140
+ clean_blob = destination_blob.lstrip('/')
141
+ encoded_blob = urllib.parse.quote(clean_blob, safe='')
142
  bucket_name = _bucket.name if _bucket and hasattr(_bucket, 'name') else _get_bucket_name() or "storage"
143
  return f"https://firebasestorage.googleapis.com/v0/b/{bucket_name}/o/{encoded_blob}?alt=media"
144
 
145
 
146
+ def _get_public_or_fallback_url(blob: Any, destination_blob: str) -> str:
147
+ """Extract public URL or fallback to media download link."""
148
+ url = None
149
+ try:
150
+ blob.make_public()
151
+ url = getattr(blob, 'public_url', None)
152
+ except Exception as pub_err:
153
+ logger.debug(f"[Firebase] make_public skipped (Uniform Bucket Access active): {pub_err}")
154
+
155
+ if not url:
156
+ url = _build_fallback_url(destination_blob)
157
+ return url
158
+
159
+
160
  def upload_bytes(
161
  data: bytes,
162
  content_type: str,
 
184
  try:
185
  blob = _bucket.blob(destination_blob)
186
  blob.upload_from_string(data, content_type=content_type)
187
+ url = _get_public_or_fallback_url(blob, destination_blob)
 
 
 
 
 
 
 
 
 
 
188
  logger.info(f"[Firebase] Uploaded {destination_blob} ({len(data)} bytes)")
189
  return url
190
 
 
220
  pass
221
 
222
  blob.upload_from_file(fileobj, content_type=content_type)
223
+ url = _get_public_or_fallback_url(blob, destination_blob)
 
 
 
 
 
 
 
 
 
 
224
  logger.info(f"[Firebase] Uploaded fileobj to {destination_blob}")
225
  return url
226