77from apscheduler .schedulers .background import BackgroundScheduler
88import atexit
99
10+ def get_currency_symbol (currency_code ):
11+ """Get currency symbol for display"""
12+ currency_symbols = {
13+ 'USD' : '$' , 'EUR' : '€' , 'GBP' : '£' , 'JPY' : '¥' , 'CHF' : 'CHF ' ,
14+ 'CAD' : 'C$' , 'AUD' : 'A$' , 'NZD' : 'NZ$' , 'SEK' : 'kr' , 'NOK' : 'kr' ,
15+ 'DKK' : 'kr' , 'PLN' : 'zł' , 'CZK' : 'Kč' , 'HUF' : 'Ft' , 'RON' : 'lei' ,
16+ 'BGN' : 'лв' , 'HRK' : 'kn' , 'RSD' : 'RSD' , 'TRY' : '₺' , 'RUB' : '₽' ,
17+ 'CNY' : '¥' , 'INR' : '₹' , 'KRW' : '₩' , 'SGD' : 'S$' , 'HKD' : 'HK$' ,
18+ 'MYR' : 'RM' , 'THB' : '฿' , 'PHP' : '₱' , 'IDR' : 'Rp' , 'VND' : '₫' ,
19+ 'BRL' : 'R$' , 'ARS' : '$' , 'CLP' : '$' , 'COP' : '$' , 'PEN' : 'S/' ,
20+ 'MXN' : '$' , 'ZAR' : 'R' , 'EGP' : 'E£' , 'MAD' : 'MAD' , 'NGN' : '₦'
21+ }
22+ return currency_symbols .get (currency_code , currency_code + ' ' )
23+
1024def format_date_for_user (date_obj , user ):
1125 """Format date based on user's date format preference"""
1226 if not date_obj :
@@ -29,6 +43,11 @@ def format_date_for_user(date_obj, user):
2943def create_email_body (user , subscriptions ):
3044 """Create HTML email body for subscription notifications"""
3145
46+ # Get user's preferred currency
47+ user_settings = user .settings or UserSettings ()
48+ user_currency = user_settings .currency or 'EUR'
49+ currency_symbol = get_currency_symbol (user_currency )
50+
3251 html_body = f"""
3352 <html>
3453 <head>
@@ -66,13 +85,17 @@ def create_email_body(user, subscriptions):
6685 css_class = "subscription"
6786 urgency = "ℹ️ NOTICE"
6887
88+ # Get costs in user's preferred currency
89+ raw_cost = subscription .get_raw_cost_in_currency (user_currency )
90+ monthly_cost = subscription .get_monthly_cost_in_currency (user_currency )
91+
6992 html_body += f"""
7093 <div class="{ css_class } ">
7194 <h3>{ urgency } - { subscription .name } </h3>
7295 <p><strong>Company:</strong> { subscription .company } </p>
7396 <p><strong>Category:</strong> { subscription .category or 'Not specified' } </p>
74- <p><strong>Cost:</strong> <span class="cost">$ { subscription . cost :.2f} </span> ({ subscription .billing_cycle } )</p>
75- <p><strong>Monthly Cost:</strong> <span class="cost">$ { subscription . get_monthly_cost () :.2f} </span></p>
97+ <p><strong>Cost:</strong> <span class="cost">{ currency_symbol } { raw_cost :.2f} </span> ({ subscription .billing_cycle } )</p>
98+ <p><strong>Monthly Cost:</strong> <span class="cost">{ currency_symbol } { monthly_cost :.2f} </span></p>
7699 <p><strong>Expires in:</strong> { days_left } day(s) ({ format_date_for_user (subscription .end_date , user )} )</p>
77100 { f'<p><strong>Notes:</strong> { subscription .notes } </p>' if subscription .notes else '' }
78101 </div>
@@ -120,6 +143,10 @@ def send_expiry_notification(app, user, subscriptions):
120143 msg ['To' ] = to_email
121144
122145 # Create plain text version
146+ user_settings = user .settings or UserSettings ()
147+ user_currency = user_settings .currency or 'EUR'
148+ currency_symbol = get_currency_symbol (user_currency )
149+
123150 text_body = f"""
124151 Hello { user .username } ,
125152
@@ -129,11 +156,14 @@ def send_expiry_notification(app, user, subscriptions):
129156
130157 for subscription in subscriptions :
131158 days_left = subscription .days_until_expiry ()
159+ raw_cost = subscription .get_raw_cost_in_currency (user_currency )
160+ monthly_cost = subscription .get_monthly_cost_in_currency (user_currency )
161+
132162 text_body += f"""
133163 - { subscription .name } ({ subscription .company } )
134164 Expires in { days_left } day(s) on { format_date_for_user (subscription .end_date , user )}
135- Cost: $ { subscription . cost :.2f} ({ subscription .billing_cycle } )
136- Monthly Cost: $ { subscription . get_monthly_cost () :.2f}
165+ Cost: { currency_symbol } { raw_cost :.2f} ({ subscription .billing_cycle } )
166+ Monthly Cost: { currency_symbol } { monthly_cost :.2f}
137167
138168 """
139169
@@ -176,14 +206,6 @@ def send_expiry_notification(app, user, subscriptions):
176206 print ("📨 Sending email..." )
177207 server .send_message (msg )
178208
179- # Update last notification date for the user (not individual subscriptions)
180- user_settings = user .settings or UserSettings ()
181- user_settings .last_notification_sent = datetime .now ().date ()
182- if not user .settings :
183- user_settings .user_id = user .id
184- db .session .add (user_settings )
185- db .session .commit ()
186-
187209 print (f"✅ Notification sent to { user .username } for { len (subscriptions )} subscriptions" )
188210 return True
189211
@@ -221,10 +243,15 @@ def check_expiring_subscriptions(app):
221243 print (f"⏭️ Skipping { user .username } - notifications disabled" )
222244 continue
223245
224- # Check if we already sent a notification today for this user
246+ # Double-check if we already sent a notification today for this user (database level check)
225247 today = current_time .date ()
248+ # Refresh user_settings from database to get latest value
249+ if user .settings :
250+ db .session .refresh (user .settings )
251+ user_settings = user .settings
252+
226253 if user_settings .last_notification_sent == today :
227- print (f"⏭️ Skipping { user .username } - already notified today" )
254+ print (f"⏭️ Skipping { user .username } - already notified today (last sent: { user_settings . last_notification_sent } ) " )
228255 continue
229256
230257 # Check if it's the user's preferred notification time (±1 hour window)
@@ -250,18 +277,35 @@ def check_expiring_subscriptions(app):
250277
251278 if expiring_subscriptions :
252279 print (f"📧 Sending notification to { user .username } for { len (expiring_subscriptions )} subscriptions at preferred time { preferred_hour } :00" )
280+
281+ # Set the notification sent flag BEFORE sending email to prevent race conditions
282+ if not user .settings :
283+ user_settings = UserSettings (user_id = user .id )
284+ db .session .add (user_settings )
285+ user_settings .last_notification_sent = today
286+ db .session .commit ()
287+
253288 success = send_expiry_notification (app , user , expiring_subscriptions )
254289 if success :
255290 total_notifications += 1
291+ print (f"✅ Notification successfully sent and marked as sent for { user .username } " )
256292 else :
257- print (f"❌ Failed to send notification to { user .username } " )
293+ # If email failed, remove the notification flag so it can be retried later
294+ user_settings .last_notification_sent = None
295+ db .session .commit ()
296+ print (f"❌ Failed to send notification to { user .username } , will retry later" )
258297 else :
259298 print (f"✅ No expiring subscriptions for { user .username } " )
260299
261300 print (f"📊 Notification check completed. Sent { total_notifications } notifications." )
262301
263302def start_scheduler (app ):
264303 """Start the background scheduler for checking expiring subscriptions"""
304+ # Check if scheduler is already running to prevent duplicates
305+ if hasattr (app , '_notification_scheduler' ) and app ._notification_scheduler :
306+ print ("⚠️ Notification scheduler already running, skipping initialization" )
307+ return
308+
265309 scheduler = BackgroundScheduler ()
266310
267311 # Check every hour to respect user-specific notification times
@@ -274,8 +318,9 @@ def start_scheduler(app):
274318 )
275319
276320 scheduler .start ()
321+ app ._notification_scheduler = scheduler
277322 atexit .register (lambda : scheduler .shutdown ())
278- print ("Email notification scheduler started (checking hourly)" )
323+ print ("✅ Email notification scheduler started (checking hourly)" )
279324
280325def send_test_email (app , user ):
281326 """Send a test email to verify email configuration"""
0 commit comments