1
- from pymongo import MongoClient
2
- from pymongo .errors import ServerSelectionTimeoutError , ConnectionFailure
3
- from werkzeug .security import generate_password_hash
4
- from datetime import datetime , timezone
5
- from app .models import User
1
+ from __future__ import annotations
2
+
6
3
import logging
7
- import time
8
- from functools import wraps
4
+ from datetime import datetime , timezone
5
+
6
+ from flask_login import current_user
9
7
from gridfs import GridFS
8
+ from werkzeug .security import generate_password_hash
9
+
10
+ from app .models import User
11
+ from app .utils import DatabaseManager , allowed_file , with_mongodb_retry
10
12
11
13
logging .basicConfig (level = logging .INFO )
12
14
logger = logging .getLogger (__name__ )
13
15
14
-
15
- def with_mongodb_retry (retries = 3 , delay = 2 ):
16
- def decorator (f ):
17
- @wraps (f )
18
- async def wrapper (* args , ** kwargs ):
19
- last_error = None
20
- for attempt in range (retries ):
21
- try :
22
- return await f (* args , ** kwargs )
23
- except (ServerSelectionTimeoutError , ConnectionFailure ) as e :
24
- last_error = e
25
- if attempt < retries - 1 : # don't sleep on last attempt
26
- logger .warning (f"Attempt { attempt + 1 } failed: { str (e )} ." )
27
- time .sleep (delay )
28
- else :
29
- logger .error (f"All { retries } attempts failed: { str (e )} " )
30
- raise last_error
31
-
32
- return wrapper
33
-
34
- return decorator
35
-
36
-
37
16
async def check_password_strength (password ):
38
17
"""
39
18
Check if password meets minimum requirements:
@@ -44,42 +23,16 @@ async def check_password_strength(password):
44
23
return True , "Password meets all requirements"
45
24
46
25
47
- class UserManager :
26
+ class UserManager ( DatabaseManager ) :
48
27
def __init__ (self , mongo_uri ):
49
- self .mongo_uri = mongo_uri
50
- self .client = None
51
- self .db = None
52
- self .connect ()
28
+ super ().__init__ (mongo_uri )
29
+ self ._ensure_collections ()
53
30
54
- def connect (self ):
55
- """Establish connection to MongoDB with basic error handling"""
56
- try :
57
- if self .client is None :
58
- self .client = MongoClient (self .mongo_uri , serverSelectionTimeoutMS = 5000 )
59
- # Test the connection
60
- self .client .server_info ()
61
- self .db = self .client .get_default_database ()
62
- logger .info ("Successfully connected to MongoDB" )
63
-
64
- # Ensure users collection exists
65
- if "users" not in self .db .list_collection_names ():
66
- self .db .create_collection ("users" )
67
- logger .info ("Created users collection" )
68
- except Exception as e :
69
- logger .error (f"Failed to connect to MongoDB: { str (e )} " )
70
- raise
71
-
72
- def ensure_connected (self ):
73
- """Ensure we have a valid connection, reconnect if necessary"""
74
- try :
75
- if self .client is None :
76
- self .connect ()
77
- else :
78
- # Test if connection is still alive
79
- self .client .server_info ()
80
- except Exception :
81
- logger .warning ("Lost connection to MongoDB, attempting to reconnect..." )
82
- self .connect ()
31
+ def _ensure_collections (self ):
32
+ """Ensure required collections exist"""
33
+ if "users" not in self .db .list_collection_names ():
34
+ self .db .create_collection ("users" )
35
+ logger .info ("Created users collection" )
83
36
84
37
@with_mongodb_retry (retries = 3 , delay = 2 )
85
38
async def create_user (
@@ -123,7 +76,7 @@ async def create_user(
123
76
124
77
except Exception as e :
125
78
logger .error (f"Error creating user: { str (e )} " )
126
- return False , f"Error creating user: { str ( e ) } "
79
+ return False , "An internal error has occurred. "
127
80
128
81
@with_mongodb_retry (retries = 3 , delay = 2 )
129
82
async def authenticate_user (self , login , password ):
@@ -191,7 +144,7 @@ async def update_user_profile(self, user_id, updates):
191
144
192
145
except Exception as e :
193
146
logger .error (f"Error updating profile: { str (e )} " )
194
- return False , f"Error updating profile: { str ( e ) } "
147
+ return False , "An internal error has occurred. "
195
148
196
149
def get_user_profile (self , username ):
197
150
"""Get user profile by username"""
@@ -210,7 +163,7 @@ async def update_profile_picture(self, user_id, file_id):
210
163
try :
211
164
from bson .objectid import ObjectId
212
165
from gridfs import GridFS
213
-
166
+
214
167
# Get the old profile picture ID first
215
168
user_data = self .db .users .find_one ({"_id" : ObjectId (user_id )})
216
169
old_picture_id = user_data .get ('profile_picture_id' ) if user_data else None
@@ -235,7 +188,7 @@ async def update_profile_picture(self, user_id, file_id):
235
188
236
189
except Exception as e :
237
190
logger .error (f"Error updating profile picture: { str (e )} " )
238
- return False , f"Error updating profile picture: { str ( e ) } "
191
+ return False , "An internal error has occurred. "
239
192
240
193
def get_profile_picture (self , user_id ):
241
194
"""Get user's profile picture ID"""
@@ -277,9 +230,45 @@ async def delete_user(self, user_id):
277
230
278
231
except Exception as e :
279
232
logger .error (f"Error deleting user: { str (e )} " )
280
- return False , f"Error deleting account: { str ( e ) } "
233
+ return False , "An internal error has occurred. "
281
234
282
- def __del__ (self ):
283
- """Cleanup MongoDB connection"""
284
- if self .client :
285
- self .client .close ()
235
+ @with_mongodb_retry (retries = 3 , delay = 2 )
236
+ async def update_user_settings (self , user_id , form_data , profile_picture = None ):
237
+ """Update user settings including profile picture"""
238
+ self .ensure_connected ()
239
+ try :
240
+ updates = {}
241
+
242
+ # Handle username update if provided
243
+ if new_username := form_data .get ('username' ):
244
+ if new_username != current_user .username :
245
+ # Check if username is taken
246
+ if self .db .users .find_one ({"username" : new_username }):
247
+ return False
248
+ updates ['username' ] = new_username
249
+
250
+ # Handle description update
251
+ if description := form_data .get ('description' ):
252
+ updates ['description' ] = description
253
+
254
+ # Handle profile picture
255
+ if profile_picture :
256
+ from werkzeug .utils import secure_filename
257
+ if profile_picture and allowed_file (profile_picture .filename ):
258
+ fs = GridFS (self .db )
259
+ filename = secure_filename (profile_picture .filename )
260
+ file_id = fs .put (
261
+ profile_picture .stream .read (),
262
+ filename = filename ,
263
+ content_type = profile_picture .content_type
264
+ )
265
+ updates ['profile_picture_id' ] = file_id
266
+
267
+ if updates :
268
+ success , message = await self .update_user_profile (user_id , updates )
269
+ return success
270
+
271
+ return True , "Profile updated successfully"
272
+ except Exception as e :
273
+ logger .error (f"Error updating user settings: { str (e )} " )
274
+ return False , "An internal error has occurred."
0 commit comments