Hamza4100 commited on
Commit
00353e4
·
verified ·
1 Parent(s): fd8d14d

Upload user_management.py

Browse files
Files changed (1) hide show
  1. src/user_management.py +418 -0
src/user_management.py ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ User Management Module
3
+ ======================
4
+ Handles user database storage in Hugging Face repository instead of local JSON.
5
+ Stores users_db.json in: Hamza4100/multi-pdf-storage/users_db.json
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import hashlib
11
+ import tempfile
12
+ from typing import Optional, Dict, Any
13
+ from datetime import datetime
14
+ from huggingface_hub import HfApi, hf_hub_download, login
15
+
16
+
17
+ class HFUserManager:
18
+ """Manages user database stored in Hugging Face repository."""
19
+
20
+ def __init__(self, hf_token: Optional[str], hf_repo: str):
21
+ """
22
+ Initialize HF User Manager.
23
+
24
+ Args:
25
+ hf_token: Hugging Face API token with write access
26
+ hf_repo: HF repository ID (e.g., "Hamza4100/multi-pdf-storage")
27
+ """
28
+ self.hf_token = hf_token
29
+ self.hf_repo = hf_repo
30
+ self.enabled = bool(hf_token and hf_repo)
31
+ self.api = None
32
+ self.users_db_file = "users_db.json" # File stored at repo root
33
+ self.users_data: Dict[str, Any] = {}
34
+
35
+ if self.enabled:
36
+ try:
37
+ login(token=hf_token, add_to_git_credential=True)
38
+ self.api = HfApi()
39
+ print(f"✅ HF User Manager initialized: {hf_repo}")
40
+ # Load users database on init
41
+ self._load_users_from_hf()
42
+ except Exception as e:
43
+ print(f"⚠️ HF User Manager initialization failed: {e}")
44
+ self.enabled = False
45
+ else:
46
+ print("⚠️ HF User Manager disabled (HF_TOKEN or HF_REPO not set)")
47
+
48
+ def _load_users_from_hf(self) -> bool:
49
+ """
50
+ Load users_db.json from HF repository.
51
+
52
+ Returns:
53
+ bool: True if loaded successfully, False otherwise
54
+ """
55
+ if not self.enabled:
56
+ return False
57
+
58
+ try:
59
+ print(f"📥 Loading users database from {self.hf_repo}/{self.users_db_file}")
60
+
61
+ # Download users_db.json from HF repo
62
+ downloaded_path = hf_hub_download(
63
+ repo_id=self.hf_repo,
64
+ filename=self.users_db_file,
65
+ token=self.hf_token,
66
+ repo_type="model",
67
+ local_dir_use_symlinks=False
68
+ )
69
+
70
+ # Read the file
71
+ with open(downloaded_path, 'r') as f:
72
+ self.users_data = json.load(f)
73
+
74
+ print(f"✅ Loaded {len(self.users_data)} user(s) from HF repo")
75
+ # Backfill missing user_id fields (derive from api_key) and save
76
+ modified = False
77
+ for username, udata in list(self.users_data.items()):
78
+ if 'user_id' not in udata and udata.get('api_key'):
79
+ try:
80
+ udata['user_id'] = hashlib.sha256(udata['api_key'].encode()).hexdigest()[:12]
81
+ self.users_data[username] = udata
82
+ modified = True
83
+ except Exception:
84
+ continue
85
+
86
+ if modified:
87
+ # Save back to HF to persist user_id fields
88
+ self._save_users_to_hf(commit_message="Backfill user_id for existing users")
89
+
90
+ return True
91
+
92
+ except Exception as e:
93
+ # File might not exist yet (first run is okay)
94
+ print(f"⚠️ Could not load users database from HF: {str(e)[:100]}")
95
+ print(" Starting with empty database (will be created on first signup)")
96
+ self.users_data = {}
97
+ return False
98
+
99
+ def _save_users_to_hf(self, commit_message: str = "Update users database") -> bool:
100
+ """
101
+ Save users_db.json to HF repository.
102
+
103
+ Args:
104
+ commit_message: Commit message for the upload
105
+
106
+ Returns:
107
+ bool: True if saved successfully, False otherwise
108
+ """
109
+ if not self.enabled:
110
+ print("⚠️ HF User Manager disabled, cannot save to HF")
111
+ return False
112
+
113
+ try:
114
+ print(f"📤 Saving users database to {self.hf_repo}/{self.users_db_file}")
115
+
116
+ # Create a temporary file with the users data
117
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tmp:
118
+ json.dump(self.users_data, tmp, indent=2)
119
+ tmp_path = tmp.name
120
+
121
+ try:
122
+ # Upload to HF repo
123
+ self.api.upload_file(
124
+ path_or_fileobj=tmp_path,
125
+ path_in_repo=self.users_db_file,
126
+ repo_id=self.hf_repo,
127
+ token=self.hf_token,
128
+ repo_type="model",
129
+ commit_message=commit_message
130
+ )
131
+
132
+ print(f"✅ Users database saved to HF repo")
133
+ return True
134
+
135
+ finally:
136
+ # Clean up temporary file
137
+ if os.path.exists(tmp_path):
138
+ os.remove(tmp_path)
139
+
140
+ except Exception as e:
141
+ print(f"❌ Failed to save users database to HF: {e}")
142
+ return False
143
+
144
+ def get_user(self, username: str) -> Optional[Dict[str, Any]]:
145
+ """
146
+ Get user by username.
147
+
148
+ Args:
149
+ username: Username to retrieve
150
+
151
+ Returns:
152
+ User data dict or None if not found
153
+ """
154
+ return self.users_data.get(username)
155
+
156
+ def user_exists(self, username: str) -> bool:
157
+ """
158
+ Check if user exists.
159
+
160
+ Args:
161
+ username: Username to check
162
+
163
+ Returns:
164
+ True if user exists, False otherwise
165
+ """
166
+ return username in self.users_data
167
+
168
+ def create_user(
169
+ self,
170
+ username: str,
171
+ password_hash: str,
172
+ email: str,
173
+ api_key: str
174
+ ) -> bool:
175
+ """
176
+ Create a new user account.
177
+
178
+ Args:
179
+ username: Username
180
+ password_hash: Hashed password
181
+ email: User email
182
+ api_key: API key for this user
183
+
184
+ Returns:
185
+ bool: True if user created, False if user already exists
186
+ """
187
+ if self.user_exists(username):
188
+ return False
189
+
190
+ # Derive stable user_id from API key for storage isolation
191
+ try:
192
+ user_id = hashlib.sha256(api_key.encode()).hexdigest()[:12]
193
+ except Exception:
194
+ user_id = None
195
+
196
+ self.users_data[username] = {
197
+ "password_hash": password_hash,
198
+ "email": email,
199
+ "api_key": api_key,
200
+ "user_id": user_id,
201
+ "created_at": datetime.now().isoformat()
202
+ }
203
+
204
+ # Save to HF
205
+ success = self._save_users_to_hf(
206
+ commit_message=f"Add new user: {username}"
207
+ )
208
+
209
+ if success:
210
+ print(f"✅ User '{username}' created and saved to HF")
211
+
212
+ return success
213
+
214
+ def update_user(self, username: str, updates: Dict[str, Any]) -> bool:
215
+ """
216
+ Update user data.
217
+
218
+ Args:
219
+ username: Username to update
220
+ updates: Dictionary with updates
221
+
222
+ Returns:
223
+ bool: True if updated successfully, False if user not found
224
+ """
225
+ if not self.user_exists(username):
226
+ return False
227
+
228
+ self.users_data[username].update(updates)
229
+
230
+ # Save to HF
231
+ success = self._save_users_to_hf(
232
+ commit_message=f"Update user: {username}"
233
+ )
234
+
235
+ if success:
236
+ print(f"✅ User '{username}' updated in HF")
237
+
238
+ return success
239
+
240
+ def delete_user(self, username: str) -> bool:
241
+ """
242
+ Delete a user account.
243
+
244
+ Args:
245
+ username: Username to delete
246
+
247
+ Returns:
248
+ bool: True if deleted successfully, False if user not found
249
+ """
250
+ if not self.user_exists(username):
251
+ return False
252
+
253
+ del self.users_data[username]
254
+
255
+ # Save to HF
256
+ success = self._save_users_to_hf(
257
+ commit_message=f"Delete user: {username}"
258
+ )
259
+
260
+ if success:
261
+ print(f"✅ User '{username}' deleted from HF")
262
+
263
+ return success
264
+
265
+ def get_all_users(self) -> Dict[str, Any]:
266
+ """
267
+ Get all users data.
268
+
269
+ Returns:
270
+ Dictionary of all users
271
+ """
272
+ return self.users_data.copy()
273
+
274
+ def get_user_by_api_key(self, api_key: str) -> Optional[tuple]:
275
+ """
276
+ Find user by API key.
277
+
278
+ Args:
279
+ api_key: API key to search for
280
+
281
+ Returns:
282
+ Tuple of (username, user_data) or None if not found
283
+ """
284
+ for username, user_data in self.users_data.items():
285
+ if user_data.get("api_key") == api_key:
286
+ return (username, user_data)
287
+
288
+ return None
289
+
290
+ def get_user_by_email(self, email: str) -> Optional[tuple]:
291
+ """
292
+ Find user by email.
293
+
294
+ Args:
295
+ email: Email to search for
296
+
297
+ Returns:
298
+ Tuple of (username, user_data) or None if not found
299
+ """
300
+ for username, user_data in self.users_data.items():
301
+ if user_data.get("email") == email:
302
+ return (username, user_data)
303
+
304
+ return None
305
+
306
+ def verify_password(self, username: str, password_hash: str) -> bool:
307
+ """
308
+ Verify user password hash.
309
+
310
+ Args:
311
+ username: Username
312
+ password_hash: Password hash to verify
313
+
314
+ Returns:
315
+ bool: True if password matches, False otherwise
316
+ """
317
+ user = self.get_user(username)
318
+ if not user:
319
+ return False
320
+
321
+ return user.get("password_hash") == password_hash
322
+
323
+ # ============================================
324
+ # USER STORAGE FILE HELPERS
325
+ # ============================================
326
+ def _path_in_repo_for_user(self, user_id: str, filename: str) -> str:
327
+ return f"users/{user_id}/{filename}"
328
+
329
+ def save_user_json(self, user_id: str, filename: str, data: dict, commit_message: str = None) -> bool:
330
+ """
331
+ Save a JSON file into the user's storage folder in the HF repo.
332
+ """
333
+ if not self.enabled:
334
+ print("⚠️ HF User Manager disabled, cannot save user file")
335
+ return False
336
+
337
+ if commit_message is None:
338
+ commit_message = f"Update {user_id}/{filename}"
339
+
340
+ try:
341
+ # Create temp file
342
+ import tempfile
343
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tmp:
344
+ json.dump(data, tmp, indent=2, ensure_ascii=False)
345
+ tmp_path = tmp.name
346
+
347
+ path_in_repo = self._path_in_repo_for_user(user_id, filename)
348
+
349
+ try:
350
+ self.api.upload_file(
351
+ path_or_fileobj=tmp_path,
352
+ path_in_repo=path_in_repo,
353
+ repo_id=self.hf_repo,
354
+ token=self.hf_token,
355
+ repo_type='model',
356
+ commit_message=commit_message
357
+ )
358
+ print(f"✅ Saved {path_in_repo} to HF repo")
359
+ return True
360
+ finally:
361
+ if os.path.exists(tmp_path):
362
+ os.remove(tmp_path)
363
+
364
+ except Exception as e:
365
+ print(f"❌ Failed to save user file to HF: {e}")
366
+ return False
367
+
368
+ def load_user_json(self, user_id: str, filename: str) -> Optional[dict]:
369
+ """
370
+ Load a JSON file from the user's storage folder in the HF repo.
371
+ Returns the parsed JSON dict or None if not found / error.
372
+ """
373
+ if not self.enabled:
374
+ return None
375
+
376
+ path_in_repo = self._path_in_repo_for_user(user_id, filename)
377
+ try:
378
+ downloaded_path = hf_hub_download(
379
+ repo_id=self.hf_repo,
380
+ filename=path_in_repo,
381
+ token=self.hf_token,
382
+ repo_type='model',
383
+ local_dir_use_symlinks=False
384
+ )
385
+
386
+ with open(downloaded_path, 'r', encoding='utf-8') as f:
387
+ return json.load(f)
388
+
389
+ except Exception as e:
390
+ # Not found or error
391
+ return None
392
+
393
+
394
+ # ============================================
395
+ # CONVENIENCE FUNCTIONS
396
+ # ============================================
397
+
398
+ def create_hf_user_manager(
399
+ hf_token: Optional[str] = None,
400
+ hf_repo: Optional[str] = None
401
+ ) -> HFUserManager:
402
+ """
403
+ Create and return an HF User Manager instance.
404
+
405
+ Args:
406
+ hf_token: HF token (reads from env if not provided)
407
+ hf_repo: HF repo ID (reads from env if not provided)
408
+
409
+ Returns:
410
+ HFUserManager instance
411
+ """
412
+ if hf_token is None:
413
+ hf_token = os.environ.get("HF_TOKEN")
414
+
415
+ if hf_repo is None:
416
+ hf_repo = os.environ.get("HF_REPO", "Hamza4100/multi-pdf-storage")
417
+
418
+ return HFUserManager(hf_token=hf_token, hf_repo=hf_repo)