Преглед на файлове

Add Google OAuth login and callback endpoints

Implemented Google OAuth login and callback logic in UserBusinessImpl and exposed new endpoints in UserController. Refactored and improved OTP logic, updated project references, and made minor code style and formatting improvements. Updated documentation diagrams and fixed some configuration and path issues.
hieubt преди 2 седмици
родител
ревизия
1f6c0f1082

+ 6 - 23
EsimLao/Common/Constant/CommonConstant.cs

@@ -17,7 +17,6 @@ public static class CommonConstant
     public const string NextQuestionFail = "-8";
     public const string NextQuestionFail = "-8";
     public const string OtpWrong = "-9";
     public const string OtpWrong = "-9";
 
 
-
     public const string Unauthorized = "-401";
     public const string Unauthorized = "-401";
     public const string BadRequest = "-402";
     public const string BadRequest = "-402";
 
 
@@ -29,7 +28,6 @@ public static class CommonConstant
     public const string ANSWER_RIGHT = "RIGHT";
     public const string ANSWER_RIGHT = "RIGHT";
     public const string ANSWER_WRONG = "WRONG";
     public const string ANSWER_WRONG = "WRONG";
 
 
-
     public const String PathImage = "/outside";
     public const String PathImage = "/outside";
 
 
     public const String MissitonTypeLOGIN = "LOGIN";
     public const String MissitonTypeLOGIN = "LOGIN";
@@ -37,7 +35,6 @@ public static class CommonConstant
     public const String MissitonTypeREGISTERS = "REGISTERS";
     public const String MissitonTypeREGISTERS = "REGISTERS";
     public const String MissitonTypeINVITING = "INVITING";
     public const String MissitonTypeINVITING = "INVITING";
 
 
-
     public const int StatusActive = 1;
     public const int StatusActive = 1;
     public const int StatusInactive = 0;
     public const int StatusInactive = 0;
     public const int StatusNotClaimed = 2;
     public const int StatusNotClaimed = 2;
@@ -58,13 +55,11 @@ public static class CommonConstant
     public const string TypeInviting = "INVITING";
     public const string TypeInviting = "INVITING";
     public const string TypeRenewService = "RENEW";
     public const string TypeRenewService = "RENEW";
 
 
-
     public const string PathOutside = "PathOutside";
     public const string PathOutside = "PathOutside";
 
 
     public const string CheckSub = "CHECKSUB";
     public const string CheckSub = "CHECKSUB";
     public const string Subscribe = "SUBSCRIBE";
     public const string Subscribe = "SUBSCRIBE";
 
 
-
     public static readonly List<int> ListStatusInt = new List<int> { StatusActive, StatusInactive };
     public static readonly List<int> ListStatusInt = new List<int> { StatusActive, StatusInactive };
     public static readonly List<string> ListCampaignType = new List<string> { "EU" };
     public static readonly List<string> ListCampaignType = new List<string> { "EU" };
     public static readonly List<string> ListMissionType = new List<string>
     public static readonly List<string> ListMissionType = new List<string>
@@ -83,9 +78,10 @@ public static class CommonConstant
         "REST_POST",
         "REST_POST",
         "REST_GET"
         "REST_GET"
     };
     };
-    public static readonly List<string> ListServiceActionCode = new List<string> { 
+    public static readonly List<string> ListServiceActionCode = new List<string>
+    {
         "REGISTER",
         "REGISTER",
-        "BUY_TURN", 
+        "BUY_TURN",
     };
     };
 
 
     public static readonly List<string> ListVendorPackagePeriod = new List<string>
     public static readonly List<string> ListVendorPackagePeriod = new List<string>
@@ -102,18 +98,9 @@ public static class CommonConstant
         "VIP"
         "VIP"
     };
     };
 
 
-    public static readonly List<string> ListMoneyType = new List<string>
-    {
-        "CENT",
-        "USD",
-        "EUR"
-    };
+    public static readonly List<string> ListMoneyType = new List<string> { "CENT", "USD", "EUR" };
 
 
-    public static readonly List<string> ListPrizeType = new List<string>
-    {
-        "POINT",
-        "MONEY"
-    };
+    public static readonly List<string> ListPrizeType = new List<string> { "POINT", "MONEY" };
 
 
     public const string DateTimeFormar1 = "dd/MM/yyyy HH:mm:ss";
     public const string DateTimeFormar1 = "dd/MM/yyyy HH:mm:ss";
     public const string DateTimeFormar2 = "MMMM yyyy";
     public const string DateTimeFormar2 = "MMMM yyyy";
@@ -298,9 +285,5 @@ public static class CommonErrorCode
     public const string DuplicateEntry = "-804";
     public const string DuplicateEntry = "-804";
 
 
     // Prize Types (kept for backward compatibility)
     // Prize Types (kept for backward compatibility)
-    public static readonly List<string> ListPrizeType = new List<string>
-    {
-        "POINT",
-        "MONEY"
-    };
+    public static readonly List<string> ListPrizeType = new List<string> { "POINT", "MONEY" };
 }
 }

+ 251 - 156
EsimLao/Esim.Apis/Business/User/UserBusinessImpl.cs

@@ -1,22 +1,22 @@
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
 using Common;
 using Common;
 using Common.Constant;
 using Common.Constant;
 using Common.Http;
 using Common.Http;
 using Common.Logic;
 using Common.Logic;
-//using Esim.Apis.Logic;
-using Esim.Apis.Singleton;
 using Database;
 using Database;
 using Database.Database;
 using Database.Database;
+//using Esim.Apis.Logic;
+using Esim.Apis.Singleton;
 using log4net;
 using log4net;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.VisualBasic;
 using Microsoft.VisualBasic;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
-using System;
-using System.Threading.Tasks;
-using System.Xml.Serialization;
-using System.Net.Http;
-using System.Net.Http.Headers;
 using Newtonsoft.Json.Linq;
 using Newtonsoft.Json.Linq;
-using System.Linq;
 
 
 namespace Esim.Apis.Business
 namespace Esim.Apis.Business
 {
 {
@@ -40,7 +40,6 @@ namespace Esim.Apis.Business
             return configuration.GetSection(key).Value ?? "";
             return configuration.GetSection(key).Value ?? "";
         }
         }
 
 
-
         /// <summary>
         /// <summary>
         /// Request OTP to be sent to email
         /// Request OTP to be sent to email
         /// </summary>
         /// </summary>
@@ -68,8 +67,8 @@ namespace Esim.Apis.Business
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
 
 
                 // Check if customer exists, if not create new
                 // Check if customer exists, if not create new
-                var customer = dbContext.CustomerInfos
-                    .Where(c => c.Email == request.email)
+                var customer = dbContext
+                    .CustomerInfos.Where(c => c.Email == request.email)
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 
                 decimal? customerId = customer?.Id;
                 decimal? customerId = customer?.Id;
@@ -77,11 +76,14 @@ namespace Esim.Apis.Business
                 if (customer == null)
                 if (customer == null)
                 {
                 {
                     // Create new customer record - manually get ID from Oracle sequence
                     // Create new customer record - manually get ID from Oracle sequence
-                    var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
-                    
+                    var newCustomerId = await Database.DbLogic.GenIdAsync(
+                        dbContext,
+                        "CUSTOMER_INFO_SEQ"
+                    );
+
                     // Extract name from email (part before @)
                     // Extract name from email (part before @)
                     string emailUsername = request.email.Split('@')[0];
                     string emailUsername = request.email.Split('@')[0];
-                    
+
                     var newCustomer = new CustomerInfo
                     var newCustomer = new CustomerInfo
                     {
                     {
                         Id = newCustomerId,
                         Id = newCustomerId,
@@ -101,48 +103,60 @@ namespace Esim.Apis.Business
                 // Check if there's an existing OTP record for this email (ANY record, used or unused)
                 // Check if there's an existing OTP record for this email (ANY record, used or unused)
                 int otpExpireMinutes = 5;
                 int otpExpireMinutes = 5;
                 int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
                 int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
-                
-                var existingOtp = dbContext.OtpVerifications
-                    .Where(o => o.UserEmail == request.email)
+
+                var existingOtp = dbContext
+                    .OtpVerifications.Where(o => o.UserEmail == request.email)
                     .OrderByDescending(o => o.CreatedDate)
                     .OrderByDescending(o => o.CreatedDate)
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 
                 if (existingOtp != null)
                 if (existingOtp != null)
                 {
                 {
                     // Anti-spam check: prevent rapid OTP requests
                     // Anti-spam check: prevent rapid OTP requests
-                    var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
+                    var secondsSinceLastRequest = (
+                        DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
+                    ).TotalSeconds;
                     if (secondsSinceLastRequest < minSecondsBetweenRequests)
                     if (secondsSinceLastRequest < minSecondsBetweenRequests)
                     {
                     {
-                        var waitSeconds = (int)(minSecondsBetweenRequests - secondsSinceLastRequest);
-                        log.Warn($"Spam prevention: OTP request too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago.");
-                        
+                        var waitSeconds = (int)(
+                            minSecondsBetweenRequests - secondsSinceLastRequest
+                        );
+                        log.Warn(
+                            $"Spam prevention: OTP request too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
+                        );
+
                         return DotnetLib.Http.HttpResponse.BuildResponse(
                         return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             log,
                             url,
                             url,
                             json,
                             json,
                             CommonErrorCode.OtpTooManyRequests,
                             CommonErrorCode.OtpTooManyRequests,
                             $"Please wait {waitSeconds} seconds before requesting a new OTP.",
                             $"Please wait {waitSeconds} seconds before requesting a new OTP.",
-                            new { 
+                            new
+                            {
                                 waitSeconds = waitSeconds,
                                 waitSeconds = waitSeconds,
-                                canRequestAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenRequests)
+                                canRequestAt = existingOtp.CreatedDate?.AddSeconds(
+                                    minSecondsBetweenRequests
+                                )
                             }
                             }
                         );
                         );
                     }
                     }
-                    
+
                     // UPDATE existing record - reuse for this email to prevent table bloat
                     // UPDATE existing record - reuse for this email to prevent table bloat
                     existingOtp.OtpCode = otpCode;
                     existingOtp.OtpCode = otpCode;
                     existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                     existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                     existingOtp.AttemptCount = 0; // Reset attempt count
                     existingOtp.AttemptCount = 0; // Reset attempt count
-                    existingOtp.IsUsed = false;   // Reset to unused (allow reuse)
+                    existingOtp.IsUsed = false; // Reset to unused (allow reuse)
                     existingOtp.CustomerId = customerId; // Update customer ID if changed
                     existingOtp.CustomerId = customerId; // Update customer ID if changed
                     existingOtp.CreatedDate = DateTime.Now; // Update to current time
                     existingOtp.CreatedDate = DateTime.Now; // Update to current time
-                    
-                    log.Info($"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})");
+
+                    log.Info(
+                        $"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})"
+                    );
                 }
                 }
                 else
                 else
                 {
                 {
                     // INSERT new record only for first-time email
                     // INSERT new record only for first-time email
-                    var otpId = (int)await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
+                    var otpId = (int)
+                        await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
                     var otpVerification = new OtpVerification
                     var otpVerification = new OtpVerification
                     {
                     {
                         Id = otpId,
                         Id = otpId,
@@ -171,8 +185,9 @@ namespace Esim.Apis.Business
                     string templateCode = "OTP_LOGIN";
                     string templateCode = "OTP_LOGIN";
 
 
                     // Query template and get language-specific content
                     // Query template and get language-specific content
-                    var template = dbContext.MessageTemplates
-                        .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
+                    var template = dbContext.MessageTemplates.FirstOrDefault(
+                        t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
+                    );
 
 
                     if (template == null)
                     if (template == null)
                     {
                     {
@@ -181,14 +196,20 @@ namespace Esim.Apis.Business
                     }
                     }
 
 
                     // Get subject based on language: vi=default, en=_EN, lo=_LO (default)
                     // Get subject based on language: vi=default, en=_EN, lo=_LO (default)
-                    string emailSubject = lang == "en" ? (template.SubjectEn ?? template.Subject ?? "")
-                                        : lang == "vi" ? (template.Subject ?? "")
-                                        : (template.SubjectLo ?? template.Subject ?? "");
+                    string emailSubject =
+                        lang == "en"
+                            ? (template.SubjectEn ?? template.Subject ?? "")
+                            : lang == "vi"
+                                ? (template.Subject ?? "")
+                                : (template.SubjectLo ?? template.Subject ?? "");
 
 
                     // Get content based on language: vi=default, en=_EN, lo=_LO (default)
                     // Get content based on language: vi=default, en=_EN, lo=_LO (default)
-                    string emailContent = lang == "en" ? (template.ContentEn ?? template.Content ?? "")
-                                        : lang == "vi" ? (template.Content ?? "")
-                                        : (template.ContentLo ?? template.Content ?? "");
+                    string emailContent =
+                        lang == "en"
+                            ? (template.ContentEn ?? template.Content ?? "")
+                            : lang == "vi"
+                                ? (template.Content ?? "")
+                                : (template.ContentLo ?? template.Content ?? "");
 
 
                     // Replace placeholders in content
                     // Replace placeholders in content
                     emailContent = emailContent
                     emailContent = emailContent
@@ -199,14 +220,15 @@ namespace Esim.Apis.Business
                     emailSubject = emailSubject
                     emailSubject = emailSubject
                         .Replace("{{OTP_CODE}}", otpCode)
                         .Replace("{{OTP_CODE}}", otpCode)
                         .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
                         .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
-                    var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
+                    var emailMessageID = (int)
+                        await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
                     var emailMessage = new MessageQueue
                     var emailMessage = new MessageQueue
                     {
                     {
                         Id = emailMessageID,
                         Id = emailMessageID,
                         MessageType = 1, // Email
                         MessageType = 1, // Email
                         Recipient = request.email,
                         Recipient = request.email,
-                        Subject = emailSubject,     // Pre-resolved subject
-                        Content = emailContent,     // Pre-resolved content
+                        Subject = emailSubject, // Pre-resolved subject
+                        Content = emailContent, // Pre-resolved content
                         Priority = true, // High priority
                         Priority = true, // High priority
                         Status = 0, // Pending
                         Status = 0, // Pending
                         ScheduledAt = DateTime.Now,
                         ScheduledAt = DateTime.Now,
@@ -220,7 +242,9 @@ namespace Esim.Apis.Business
                     await dbContext.SaveChangesAsync();
                     await dbContext.SaveChangesAsync();
                 }
                 }
 
 
-                log.Info($"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
+                log.Info(
+                    $"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
+                );
 
 
                 //return DotnetLib.Http.HttpResponse.BuildResponse(
                 //return DotnetLib.Http.HttpResponse.BuildResponse(
                 //    log,
                 //    log,
@@ -241,11 +265,7 @@ namespace Esim.Apis.Business
                     json,
                     json,
                     CommonErrorCode.Success,
                     CommonErrorCode.Success,
                     ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
                     ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
-                    new
-                    {
-                        email = request.email,
-                        expireInSeconds = otpExpireMinutes * 60
-                    }
+                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                 );
                 );
             }
             }
             catch (Exception exception)
             catch (Exception exception)
@@ -271,11 +291,11 @@ namespace Esim.Apis.Business
             var url = httpRequest.Path;
             var url = httpRequest.Path;
             var json = JsonConvert.SerializeObject(request);
             var json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => ResendOtp Request: " + json);
             log.Debug("URL: " + url + " => ResendOtp Request: " + json);
-            
+
             try
             try
             {
             {
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
-                
+
                 // Validate email is required
                 // Validate email is required
                 if (string.IsNullOrEmpty(request.email))
                 if (string.IsNullOrEmpty(request.email))
                 {
                 {
@@ -290,8 +310,8 @@ namespace Esim.Apis.Business
                 }
                 }
 
 
                 // Check if there's an existing OTP record for this email
                 // Check if there's an existing OTP record for this email
-                var existingOtp = dbContext.OtpVerifications
-                    .Where(o => o.UserEmail == request.email)
+                var existingOtp = dbContext
+                    .OtpVerifications.Where(o => o.UserEmail == request.email)
                     .OrderByDescending(o => o.CreatedDate)
                     .OrderByDescending(o => o.CreatedDate)
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 
@@ -311,22 +331,29 @@ namespace Esim.Apis.Business
 
 
                 // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
                 // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
                 int minSecondsBetweenResend = 60;
                 int minSecondsBetweenResend = 60;
-                var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
-                
+                var secondsSinceLastRequest = (
+                    DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
+                ).TotalSeconds;
+
                 if (secondsSinceLastRequest < minSecondsBetweenResend)
                 if (secondsSinceLastRequest < minSecondsBetweenResend)
                 {
                 {
                     var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
                     var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
-                    log.Warn($"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago.");
-                    
+                    log.Warn(
+                        $"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
+                    );
+
                     return DotnetLib.Http.HttpResponse.BuildResponse(
                     return DotnetLib.Http.HttpResponse.BuildResponse(
                         log,
                         log,
                         url,
                         url,
                         json,
                         json,
                         CommonErrorCode.OtpTooManyRequests,
                         CommonErrorCode.OtpTooManyRequests,
                         $"Please wait {waitSeconds} seconds before resending OTP.",
                         $"Please wait {waitSeconds} seconds before resending OTP.",
-                        new { 
+                        new
+                        {
                             waitSeconds = waitSeconds,
                             waitSeconds = waitSeconds,
-                            canResendAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenResend)
+                            canResendAt = existingOtp.CreatedDate?.AddSeconds(
+                                minSecondsBetweenResend
+                            )
                         }
                         }
                     );
                     );
                 }
                 }
@@ -334,13 +361,13 @@ namespace Esim.Apis.Business
                 // Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
                 // Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
                 bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
                 bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
-                
+
                 // OTP expires in 5 minutes
                 // OTP expires in 5 minutes
                 int otpExpireMinutes = 5;
                 int otpExpireMinutes = 5;
 
 
                 // Get customer ID (should exist since OTP record exists)
                 // Get customer ID (should exist since OTP record exists)
-                var customer = dbContext.CustomerInfos
-                    .Where(c => c.Email == request.email)
+                var customer = dbContext
+                    .CustomerInfos.Where(c => c.Email == request.email)
                     .FirstOrDefault();
                     .FirstOrDefault();
                 decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
                 decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
 
 
@@ -348,12 +375,12 @@ namespace Esim.Apis.Business
                 existingOtp.OtpCode = otpCode;
                 existingOtp.OtpCode = otpCode;
                 existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                 existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                 existingOtp.AttemptCount = 0; // Reset attempt count
                 existingOtp.AttemptCount = 0; // Reset attempt count
-                existingOtp.IsUsed = false;   // Reset to unused
+                existingOtp.IsUsed = false; // Reset to unused
                 existingOtp.CustomerId = customerId;
                 existingOtp.CustomerId = customerId;
                 existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
                 existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
-                
+
                 log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
                 log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
-                
+
                 await dbContext.SaveChangesAsync();
                 await dbContext.SaveChangesAsync();
 
 
                 // Skip email sending for test account
                 // Skip email sending for test account
@@ -361,9 +388,10 @@ namespace Esim.Apis.Business
                 {
                 {
                     // Add to MESSAGE_QUEUE for background email sending
                     // Add to MESSAGE_QUEUE for background email sending
                     string templateCode = "OTP_LOGIN";
                     string templateCode = "OTP_LOGIN";
-                    
-                    var template = dbContext.MessageTemplates
-                        .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
+
+                    var template = dbContext.MessageTemplates.FirstOrDefault(
+                        t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
+                    );
 
 
                     if (template == null)
                     if (template == null)
                     {
                     {
@@ -372,14 +400,20 @@ namespace Esim.Apis.Business
                     }
                     }
 
 
                     // Get subject based on language
                     // Get subject based on language
-                    string emailSubject = lang == "en" ? (template.SubjectEn ?? template.Subject ?? "")
-                                        : lang == "vi" ? (template.Subject ?? "")
-                                        : (template.SubjectLo ?? template.Subject ?? "");
+                    string emailSubject =
+                        lang == "en"
+                            ? (template.SubjectEn ?? template.Subject ?? "")
+                            : lang == "vi"
+                                ? (template.Subject ?? "")
+                                : (template.SubjectLo ?? template.Subject ?? "");
 
 
                     // Get content based on language
                     // Get content based on language
-                    string emailContent = lang == "en" ? (template.ContentEn ?? template.Content ?? "")
-                                        : lang == "vi" ? (template.Content ?? "")
-                                        : (template.ContentLo ?? template.Content ?? "");
+                    string emailContent =
+                        lang == "en"
+                            ? (template.ContentEn ?? template.Content ?? "")
+                            : lang == "vi"
+                                ? (template.Content ?? "")
+                                : (template.ContentLo ?? template.Content ?? "");
 
 
                     // Replace placeholders
                     // Replace placeholders
                     emailContent = emailContent
                     emailContent = emailContent
@@ -390,7 +424,8 @@ namespace Esim.Apis.Business
                         .Replace("{{OTP_CODE}}", otpCode)
                         .Replace("{{OTP_CODE}}", otpCode)
                         .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
                         .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
 
 
-                    var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
+                    var emailMessageID = (int)
+                        await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
                     var emailMessage = new MessageQueue
                     var emailMessage = new MessageQueue
                     {
                     {
                         Id = emailMessageID,
                         Id = emailMessageID,
@@ -411,7 +446,9 @@ namespace Esim.Apis.Business
                     await dbContext.SaveChangesAsync();
                     await dbContext.SaveChangesAsync();
                 }
                 }
 
 
-                log.Info($"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
+                log.Info(
+                    $"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
+                );
 
 
                 return DotnetLib.Http.HttpResponse.BuildResponse(
                 return DotnetLib.Http.HttpResponse.BuildResponse(
                     log,
                     log,
@@ -419,11 +456,7 @@ namespace Esim.Apis.Business
                     json,
                     json,
                     CommonErrorCode.Success,
                     CommonErrorCode.Success,
                     ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
                     ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
-                    new
-                    {
-                        email = request.email,
-                        expireInSeconds = otpExpireMinutes * 60
-                    }
+                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                 );
                 );
             }
             }
             catch (Exception exception)
             catch (Exception exception)
@@ -467,19 +500,24 @@ namespace Esim.Apis.Business
                 string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
                 string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
 
 
                 // Find valid OTP
                 // Find valid OTP
-                var otpRecord = dbContext.OtpVerifications
-                    .Where(o => o.UserEmail == request.email
-                        && o.OtpCode == request.otpCode
-                        && o.IsUsed == false
-                        && o.ExpiredAt > DateTime.Now)
+                var otpRecord = dbContext
+                    .OtpVerifications.Where(
+                        o =>
+                            o.UserEmail == request.email
+                            && o.OtpCode == request.otpCode
+                            && o.IsUsed == false
+                            && o.ExpiredAt > DateTime.Now
+                    )
                     .OrderByDescending(o => o.CreatedDate)
                     .OrderByDescending(o => o.CreatedDate)
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 
                 if (otpRecord == null)
                 if (otpRecord == null)
                 {
                 {
                     // Check if OTP exists but expired or used
                     // Check if OTP exists but expired or used
-                    var anyOtp = dbContext.OtpVerifications
-                        .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode)
+                    var anyOtp = dbContext
+                        .OtpVerifications.Where(
+                            o => o.UserEmail == request.email && o.OtpCode == request.otpCode
+                        )
                         .FirstOrDefault();
                         .FirstOrDefault();
 
 
                     if (anyOtp != null)
                     if (anyOtp != null)
@@ -491,7 +529,10 @@ namespace Esim.Apis.Business
                                 url,
                                 url,
                                 json,
                                 json,
                                 CommonErrorCode.OtpAlreadyUsed,
                                 CommonErrorCode.OtpAlreadyUsed,
-                                ConfigManager.Instance.GetConfigWebValue("OTP_ALREADY_USED", responseLang),
+                                ConfigManager.Instance.GetConfigWebValue(
+                                    "OTP_ALREADY_USED",
+                                    responseLang
+                                ),
                                 new { }
                                 new { }
                             );
                             );
                         }
                         }
@@ -502,7 +543,10 @@ namespace Esim.Apis.Business
                                 url,
                                 url,
                                 json,
                                 json,
                                 CommonErrorCode.OtpExpired,
                                 CommonErrorCode.OtpExpired,
-                                ConfigManager.Instance.GetConfigWebValue("OTP_EXPIRED", responseLang),
+                                ConfigManager.Instance.GetConfigWebValue(
+                                    "OTP_EXPIRED",
+                                    responseLang
+                                ),
                                 new { }
                                 new { }
                             );
                             );
                         }
                         }
@@ -522,8 +566,8 @@ namespace Esim.Apis.Business
                 otpRecord.IsUsed = true;
                 otpRecord.IsUsed = true;
 
 
                 // Get customer info
                 // Get customer info
-                var customer = dbContext.CustomerInfos
-                    .Where(c => c.Email == request.email)
+                var customer = dbContext
+                    .CustomerInfos.Where(c => c.Email == request.email)
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 
                 if (customer == null)
                 if (customer == null)
@@ -547,14 +591,21 @@ namespace Esim.Apis.Business
                 int tokenExpireHours = 24;
                 int tokenExpireHours = 24;
                 int refreshTokenExpireDays = 30;
                 int refreshTokenExpireDays = 30;
 
 
-                string accessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
-                string refreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
+                string accessToken = CommonLogic.GenToken(
+                    configuration,
+                    customer.Email ?? "",
+                    customer.Id.ToString() ?? ""
+                );
+                string refreshToken = CommonLogic.GenRefreshToken(
+                    configuration,
+                    customer.Email ?? ""
+                );
                 var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                 var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                 var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
                 var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
 
 
                 // Revoke old tokens
                 // Revoke old tokens
-                var oldTokens = dbContext.UserTokens
-                    .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
+                var oldTokens = dbContext
+                    .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
                     .ToList();
                     .ToList();
 
 
                 foreach (var oldToken in oldTokens)
                 foreach (var oldToken in oldTokens)
@@ -642,23 +693,26 @@ namespace Esim.Apis.Business
             return ipAddress ?? "Unknown";
             return ipAddress ?? "Unknown";
         }
         }
 
 
-        public async Task<IActionResult> GoogleLogin(HttpRequest httpRequest, GoogleLoginReq request)
+        public async Task<IActionResult> GoogleLogin(
+            HttpRequest httpRequest,
+            GoogleLoginReq request
+        )
         {
         {
             var url = httpRequest.Path;
             var url = httpRequest.Path;
             var json = JsonConvert.SerializeObject(request);
             var json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
             log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
-            
-            try 
+
+            try
             {
             {
                 string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
                 string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
-                
+
                 var clientId = configuration["Google:ClientId"];
                 var clientId = configuration["Google:ClientId"];
                 var redirectUri = configuration["Google:RedirectUri"];
                 var redirectUri = configuration["Google:RedirectUri"];
-                
+
                 if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
                 if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
                 {
                 {
                     log.Error("Google Auth configuration missing");
                     log.Error("Google Auth configuration missing");
-                     return DotnetLib.Http.HttpResponse.BuildResponse(
+                    return DotnetLib.Http.HttpResponse.BuildResponse(
                         log,
                         log,
                         url,
                         url,
                         json,
                         json,
@@ -668,21 +722,22 @@ namespace Esim.Apis.Business
                     );
                     );
                 }
                 }
 
 
-                var googleUrl = $"https://accounts.google.com/o/oauth2/v2/auth?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&scope=email%20profile";
-                
+                var googleUrl =
+                    $"https://accounts.google.com/o/oauth2/v2/auth?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&scope=email%20profile&prompt=select_account";
+
                 return DotnetLib.Http.HttpResponse.BuildResponse(
                 return DotnetLib.Http.HttpResponse.BuildResponse(
-                        log,
-                        url,
-                        json,
-                        CommonErrorCode.Success,
-                        ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
-                        new { url = googleUrl }
-                    );
+                    log,
+                    url,
+                    json,
+                    CommonErrorCode.Success,
+                    ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
+                    new { url = googleUrl }
+                );
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                 log.Error("GoogleLogin Exception: ", ex);
-                 return DotnetLib.Http.HttpResponse.BuildResponse(
+                log.Error("GoogleLogin Exception: ", ex);
+                return DotnetLib.Http.HttpResponse.BuildResponse(
                     log,
                     log,
                     url,
                     url,
                     json,
                     json,
@@ -693,20 +748,23 @@ namespace Esim.Apis.Business
             }
             }
         }
         }
 
 
-        public async Task<IActionResult> GoogleCallback(HttpRequest httpRequest, GoogleCallbackReq request)
+        public async Task<IActionResult> GoogleCallback(
+            HttpRequest httpRequest,
+            GoogleCallbackReq request
+        )
         {
         {
             var url = httpRequest.Path;
             var url = httpRequest.Path;
             var json = JsonConvert.SerializeObject(request);
             var json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
             log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
-            
+
             try
             try
             {
             {
                 // Get language for response messages
                 // Get language for response messages
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
-                
+
                 if (string.IsNullOrEmpty(request.code))
                 if (string.IsNullOrEmpty(request.code))
                 {
                 {
-                     return DotnetLib.Http.HttpResponse.BuildResponse(
+                    return DotnetLib.Http.HttpResponse.BuildResponse(
                         log,
                         log,
                         url,
                         url,
                         json,
                         json,
@@ -718,32 +776,42 @@ namespace Esim.Apis.Business
 
 
                 var clientId = configuration["Google:ClientId"];
                 var clientId = configuration["Google:ClientId"];
                 var clientSecret = configuration["Google:ClientSecret"];
                 var clientSecret = configuration["Google:ClientSecret"];
-                var redirectUri = !string.IsNullOrEmpty(request.redirectUri) ? request.redirectUri : configuration["Google:RedirectUri"];
+                var redirectUri = !string.IsNullOrEmpty(request.redirectUri)
+                    ? request.redirectUri
+                    : configuration["Google:RedirectUri"];
 
 
                 using (var httpClient = new HttpClient())
                 using (var httpClient = new HttpClient())
                 {
                 {
                     // 1. Exchange code for token
                     // 1. Exchange code for token
-                    var tokenRequestContent = new FormUrlEncodedContent(new[]
-                    {
-                        new KeyValuePair<string, string>("code", request.code),
-                        new KeyValuePair<string, string>("client_id", clientId),
-                        new KeyValuePair<string, string>("client_secret", clientSecret),
-                        new KeyValuePair<string, string>("redirect_uri", redirectUri),
-                        new KeyValuePair<string, string>("grant_type", "authorization_code")
-                    });
-
-                    var tokenResponse = await httpClient.PostAsync("https://oauth2.googleapis.com/token", tokenRequestContent);
+                    var tokenRequestContent = new FormUrlEncodedContent(
+                        new[]
+                        {
+                            new KeyValuePair<string, string>("code", request.code),
+                            new KeyValuePair<string, string>("client_id", clientId),
+                            new KeyValuePair<string, string>("client_secret", clientSecret),
+                            new KeyValuePair<string, string>("redirect_uri", redirectUri),
+                            new KeyValuePair<string, string>("grant_type", "authorization_code")
+                        }
+                    );
+
+                    var tokenResponse = await httpClient.PostAsync(
+                        "https://oauth2.googleapis.com/token",
+                        tokenRequestContent
+                    );
                     var tokenResponseString = await tokenResponse.Content.ReadAsStringAsync();
                     var tokenResponseString = await tokenResponse.Content.ReadAsStringAsync();
-                    
+
                     if (!tokenResponse.IsSuccessStatusCode)
                     if (!tokenResponse.IsSuccessStatusCode)
                     {
                     {
-                         log.Error($"Google Token Exchange Failed: {tokenResponseString}");
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        log.Error($"Google Token Exchange Failed: {tokenResponseString}");
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             log,
                             url,
                             url,
                             json,
                             json,
                             CommonErrorCode.ExternalServiceError,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_TOKEN_EXCHANGE_FAILED", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_TOKEN_EXCHANGE_FAILED",
+                                lang
+                            ),
                             new { error = tokenResponseString }
                             new { error = tokenResponseString }
                         );
                         );
                     }
                     }
@@ -753,30 +821,41 @@ namespace Esim.Apis.Business
 
 
                     if (string.IsNullOrEmpty(accessToken))
                     if (string.IsNullOrEmpty(accessToken))
                     {
                     {
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             log,
                             url,
                             url,
                             json,
                             json,
                             CommonErrorCode.ExternalServiceError,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_NO_ACCESS_TOKEN", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_NO_ACCESS_TOKEN",
+                                lang
+                            ),
                             new { }
                             new { }
                         );
                         );
                     }
                     }
 
 
                     // 2. Get User Info
                     // 2. Get User Info
-                    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
-                    var userInfoResponse = await httpClient.GetAsync("https://www.googleapis.com/oauth2/v2/userinfo");
+                    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
+                        "Bearer",
+                        accessToken
+                    );
+                    var userInfoResponse = await httpClient.GetAsync(
+                        "https://www.googleapis.com/oauth2/v2/userinfo"
+                    );
                     var userInfoString = await userInfoResponse.Content.ReadAsStringAsync();
                     var userInfoString = await userInfoResponse.Content.ReadAsStringAsync();
 
 
                     if (!userInfoResponse.IsSuccessStatusCode)
                     if (!userInfoResponse.IsSuccessStatusCode)
                     {
                     {
-                         log.Error($"Google User Info Failed: {userInfoString}");
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        log.Error($"Google User Info Failed: {userInfoString}");
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             log,
                             url,
                             url,
                             json,
                             json,
                             CommonErrorCode.ExternalServiceError,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_USERINFO_FAILED", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_USERINFO_FAILED",
+                                lang
+                            ),
                             new { error = userInfoString }
                             new { error = userInfoString }
                         );
                         );
                     }
                     }
@@ -785,10 +864,10 @@ namespace Esim.Apis.Business
                     var email = userInfo["email"]?.ToString();
                     var email = userInfo["email"]?.ToString();
                     var name = userInfo["name"]?.ToString(); // Full name
                     var name = userInfo["name"]?.ToString(); // Full name
                     var picture = userInfo["picture"]?.ToString();
                     var picture = userInfo["picture"]?.ToString();
-                    
+
                     if (string.IsNullOrEmpty(email))
                     if (string.IsNullOrEmpty(email))
                     {
                     {
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             log,
                             url,
                             url,
                             json,
                             json,
@@ -799,14 +878,16 @@ namespace Esim.Apis.Business
                     }
                     }
 
 
                     // 3. Login or Register logic
                     // 3. Login or Register logic
-                     var customer = dbContext.CustomerInfos
-                        .FirstOrDefault(c => c.Email == email);
+                    var customer = dbContext.CustomerInfos.FirstOrDefault(c => c.Email == email);
 
 
                     if (customer == null)
                     if (customer == null)
                     {
                     {
                         // Register new user
                         // Register new user
-                        var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
-                        
+                        var newCustomerId = await Database.DbLogic.GenIdAsync(
+                            dbContext,
+                            "CUSTOMER_INFO_SEQ"
+                        );
+
                         // Try to split name
                         // Try to split name
                         string surname = name;
                         string surname = name;
                         string lastname = "";
                         string lastname = "";
@@ -819,9 +900,9 @@ namespace Esim.Apis.Business
                                 surname = string.Join(" ", parts.Take(parts.Length - 1));
                                 surname = string.Join(" ", parts.Take(parts.Length - 1));
                             }
                             }
                         }
                         }
-                        else 
+                        else
                         {
                         {
-                             surname = email.Split('@')[0];
+                            surname = email.Split('@')[0];
                         }
                         }
 
 
                         customer = new CustomerInfo
                         customer = new CustomerInfo
@@ -830,7 +911,7 @@ namespace Esim.Apis.Business
                             Email = email,
                             Email = email,
                             SurName = surname,
                             SurName = surname,
                             LastName = lastname,
                             LastName = lastname,
-                            AvatarUrl = picture, 
+                            AvatarUrl = picture,
                             Status = true,
                             Status = true,
                             IsVerified = true, // Verified by Google
                             IsVerified = true, // Verified by Google
                             CreatedDate = DateTime.Now,
                             CreatedDate = DateTime.Now,
@@ -838,34 +919,48 @@ namespace Esim.Apis.Business
                         };
                         };
                         dbContext.CustomerInfos.Add(customer);
                         dbContext.CustomerInfos.Add(customer);
                         await dbContext.SaveChangesAsync();
                         await dbContext.SaveChangesAsync();
-                        log.Info($"Created new customer via Google Login: ID={newCustomerId}, Email={email}");
+                        log.Info(
+                            $"Created new customer via Google Login: ID={newCustomerId}, Email={email}"
+                        );
                     }
                     }
-                    else 
+                    else
                     {
                     {
                         // Update existing user info if needed or just log them in
                         // Update existing user info if needed or just log them in
-                        customer.IsVerified = true; 
-                        if (string.IsNullOrEmpty(customer.AvatarUrl) && !string.IsNullOrEmpty(picture))
+                        customer.IsVerified = true;
+                        if (
+                            string.IsNullOrEmpty(customer.AvatarUrl)
+                            && !string.IsNullOrEmpty(picture)
+                        )
                         {
                         {
                             customer.AvatarUrl = picture;
                             customer.AvatarUrl = picture;
                         }
                         }
                         customer.LastLoginDate = DateTime.Now;
                         customer.LastLoginDate = DateTime.Now;
                         customer.LastUpdate = DateTime.Now;
                         customer.LastUpdate = DateTime.Now;
                         await dbContext.SaveChangesAsync();
                         await dbContext.SaveChangesAsync();
-                        log.Info($"Existing customer logged in via Google: ID={customer.Id}, Email={email}");
+                        log.Info(
+                            $"Existing customer logged in via Google: ID={customer.Id}, Email={email}"
+                        );
                     }
                     }
 
 
                     // 4. Generate JWT
                     // 4. Generate JWT
                     int tokenExpireHours = 24;
                     int tokenExpireHours = 24;
                     int refreshTokenExpireDays = 30;
                     int refreshTokenExpireDays = 30;
 
 
-                    string jwtAccessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
-                    string jwtRefreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
+                    string jwtAccessToken = CommonLogic.GenToken(
+                        configuration,
+                        customer.Email ?? "",
+                        customer.Id.ToString() ?? ""
+                    );
+                    string jwtRefreshToken = CommonLogic.GenRefreshToken(
+                        configuration,
+                        customer.Email ?? ""
+                    );
                     var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                     var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                     var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
                     var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
 
 
                     // Revoke old tokens
                     // Revoke old tokens
-                    var oldTokens = dbContext.UserTokens
-                        .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
+                    var oldTokens = dbContext
+                        .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
                         .ToList();
                         .ToList();
 
 
                     foreach (var oldToken in oldTokens)
                     foreach (var oldToken in oldTokens)
@@ -874,7 +969,8 @@ namespace Esim.Apis.Business
                     }
                     }
 
 
                     // Save new token
                     // Save new token
-                    var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
+                    var tokenId = (int)
+                        await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
                     var userToken = new UserToken
                     var userToken = new UserToken
                     {
                     {
                         Id = tokenId,
                         Id = tokenId,
@@ -916,7 +1012,7 @@ namespace Esim.Apis.Business
             catch (Exception exception)
             catch (Exception exception)
             {
             {
                 log.Error("GoogleCallback Exception: ", exception);
                 log.Error("GoogleCallback Exception: ", exception);
-                 return DotnetLib.Http.HttpResponse.BuildResponse(
+                return DotnetLib.Http.HttpResponse.BuildResponse(
                     log,
                     log,
                     url,
                     url,
                     json,
                     json,
@@ -926,6 +1022,5 @@ namespace Esim.Apis.Business
                 );
                 );
             }
             }
         }
         }
-
     }
     }
 }
 }

+ 5 - 3
EsimLao/Esim.Apis/Controllers/UserController.cs

@@ -2,8 +2,8 @@ using System;
 using Common;
 using Common;
 using Common.Constant;
 using Common.Constant;
 using Common.Http;
 using Common.Http;
-using Esim.Apis.Business;
 using Database.Database;
 using Database.Database;
+using Esim.Apis.Business;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;
 
 
@@ -81,7 +81,10 @@ namespace RevoSystem.Apis.Controllers
         [Route(ApiUrlConstant.GoogleLoginUrl)]
         [Route(ApiUrlConstant.GoogleLoginUrl)]
         public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginReq request)
         public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginReq request)
         {
         {
-            return await userBusiness.GoogleLogin(HttpContext.Request, request ?? new GoogleLoginReq());
+            return await userBusiness.GoogleLogin(
+                HttpContext.Request,
+                request ?? new GoogleLoginReq()
+            );
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -97,6 +100,5 @@ namespace RevoSystem.Apis.Controllers
         {
         {
             return await userBusiness.GoogleCallback(HttpContext.Request, request);
             return await userBusiness.GoogleCallback(HttpContext.Request, request);
         }
         }
-
     }
     }
 }
 }

+ 3 - 3
EsimLao/Esim.Apis/Esim.Apis.csproj

@@ -23,12 +23,12 @@
 		<PackageReference Include="NCrontab" Version="3.3.3" />
 		<PackageReference Include="NCrontab" Version="3.3.3" />
 	</ItemGroup>
 	</ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\Common\Common.csproj" />
-    <ProjectReference Include="..\Database\Database.csproj" />
+    <ProjectReference Include="../Common/Common.csproj" />
+    <ProjectReference Include="../Database/Database.csproj" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Reference Include="DotnetLib">
     <Reference Include="DotnetLib">
-      <HintPath>..\..\..\..\lib\DotnetLib.dll</HintPath>
+      <HintPath>../lib/DotnetLib.dll</HintPath>
     </Reference>
     </Reference>
   </ItemGroup>
   </ItemGroup>
 
 

+ 56 - 46
EsimLao/Esim.Apis/Program.cs

@@ -2,33 +2,38 @@ using System.Text;
 using Common.Global;
 using Common.Global;
 using Database.Database;
 using Database.Database;
 using Esim.Apis.Business;
 using Esim.Apis.Business;
+using log4net;
+using log4net.Config;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.IdentityModel.Tokens;
 using Microsoft.IdentityModel.Tokens;
 
 
-using log4net;
-using log4net.Config;
-
 var builder = WebApplication.CreateBuilder(args);
 var builder = WebApplication.CreateBuilder(args);
 
 
 // Initialize log4net
 // Initialize log4net
-XmlConfigurator.Configure(new FileInfo(Path.Combine(builder.Environment.ContentRootPath, "log4net.config")));
+XmlConfigurator.Configure(
+    new FileInfo(Path.Combine(builder.Environment.ContentRootPath, "log4net.config"))
+);
 
 
 // Set global configuration for use by singletons like ConfigManager
 // Set global configuration for use by singletons like ConfigManager
 GlobalConfig.Configuration = builder.Configuration;
 GlobalConfig.Configuration = builder.Configuration;
 
 
 // Add services to the container.
 // Add services to the container.
-builder.Services.AddControllersWithViews()
+builder
+    .Services.AddControllersWithViews()
     .AddJsonOptions(options =>
     .AddJsonOptions(options =>
     {
     {
         // Use camelCase for JSON property names (categoryId instead of CategoryId)
         // Use camelCase for JSON property names (categoryId instead of CategoryId)
-        options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
+        options.JsonSerializerOptions.PropertyNamingPolicy = System
+            .Text
+            .Json
+            .JsonNamingPolicy
+            .CamelCase;
     });
     });
 
 
 // Add DbContext with Oracle provider
 // Add DbContext with Oracle provider
 var connectionString = builder.Configuration.GetSection("Connection").Value;
 var connectionString = builder.Configuration.GetSection("Connection").Value;
-builder.Services.AddDbContext<ModelContext>(options =>
-    options.UseOracle(connectionString));
+builder.Services.AddDbContext<ModelContext>(options => options.UseOracle(connectionString));
 
 
 // Add Business Services
 // Add Business Services
 builder.Services.AddScoped<IUserBusiness, UserBusinessImpl>();
 builder.Services.AddScoped<IUserBusiness, UserBusinessImpl>();
@@ -38,46 +43,53 @@ builder.Services.AddScoped<IContentBusiness, ContentBusinessImpl>();
 // Configure CORS - Allow frontend to access APIs
 // Configure CORS - Allow frontend to access APIs
 builder.Services.AddCors(options =>
 builder.Services.AddCors(options =>
 {
 {
-    options.AddPolicy("AllowFrontend", policy =>
-    {
-        policy.WithOrigins(
-                "http://localhost:3000",      // React development
-                "http://localhost:5173",      // Vite development
-                "http://localhost:4200",      // Angular development
-                "http://simgetgo.vn",        // Production domain
-                "http://simgetgo.com",     // Production www domain
-                "https://simgetgo.vn",    // Production www domain
-                "https://simgetgo.com"     // Production www domain
-            )
-            .AllowAnyMethod()
-            .AllowAnyHeader()
-            .AllowCredentials();
-    });
+    options.AddPolicy(
+        "AllowFrontend",
+        policy =>
+        {
+            policy
+                .WithOrigins(
+                    "http://localhost:3000", // React development
+                    "http://localhost:5173", // Vite development
+                    "http://localhost:4200", // Angular development
+                    "http://simgetgo.vn", // Production domain
+                    "http://simgetgo.com", // Production www domain
+                    "https://simgetgo.vn", // Production www domain
+                    "https://simgetgo.com" // Production www domain
+                )
+                .AllowAnyMethod()
+                .AllowAnyHeader()
+                .AllowCredentials();
+        }
+    );
 });
 });
 
 
 // Configure JWT Authentication
 // Configure JWT Authentication
-var jwtKey = builder.Configuration["Jwt:Key"] ?? "EsimLaoSecretKey1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD";
+var jwtKey =
+    builder.Configuration["Jwt:Key"]
+    ?? "EsimLaoSecretKey1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD";
 var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "EsimLao";
 var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "EsimLao";
 var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "EsimLaoClient";
 var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "EsimLaoClient";
 
 
-builder.Services.AddAuthentication(options =>
-{
-    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
-    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
-})
-.AddJwtBearer(options =>
-{
-    options.TokenValidationParameters = new TokenValidationParameters
+builder
+    .Services.AddAuthentication(options =>
     {
     {
-        ValidateIssuer = true,
-        ValidateAudience = true,
-        ValidateLifetime = true,
-        ValidateIssuerSigningKey = true,
-        ValidIssuer = jwtIssuer,
-        ValidAudience = jwtAudience,
-        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey))
-    };
-});
+        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+    })
+    .AddJwtBearer(options =>
+    {
+        options.TokenValidationParameters = new TokenValidationParameters
+        {
+            ValidateIssuer = true,
+            ValidateAudience = true,
+            ValidateLifetime = true,
+            ValidateIssuerSigningKey = true,
+            ValidIssuer = jwtIssuer,
+            ValidAudience = jwtAudience,
+            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey))
+        };
+    });
 
 
 // Add Swagger for API documentation
 // Add Swagger for API documentation
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddEndpointsApiExplorer();
@@ -94,7 +106,7 @@ using (var scope = app.Services.CreateScope())
         logger.LogInformation("Initializing ConfigManager...");
         logger.LogInformation("Initializing ConfigManager...");
         Esim.Apis.Singleton.ConfigManager.Instance.Initialize();
         Esim.Apis.Singleton.ConfigManager.Instance.Initialize();
         logger.LogInformation("ConfigManager initialized successfully");
         logger.LogInformation("ConfigManager initialized successfully");
-        
+
         // Start background refresh task
         // Start background refresh task
         Task.Run(() => Esim.Apis.Singleton.ConfigManager.Instance.RefreshConfigs());
         Task.Run(() => Esim.Apis.Singleton.ConfigManager.Instance.RefreshConfigs());
     }
     }
@@ -105,6 +117,7 @@ using (var scope = app.Services.CreateScope())
 }
 }
 app.UseSwagger();
 app.UseSwagger();
 app.UseSwaggerUI();
 app.UseSwaggerUI();
+
 // Configure the HTTP request pipeline.
 // Configure the HTTP request pipeline.
 //if (app.Environment.IsDevelopment())
 //if (app.Environment.IsDevelopment())
 //{
 //{
@@ -134,12 +147,9 @@ app.UseAuthorization();
 
 
 app.MapStaticAssets();
 app.MapStaticAssets();
 
 
-app.MapControllerRoute(
-    name: "default",
-    pattern: "{controller=Home}/{action=Index}/{id?}")
+app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}")
     .WithStaticAssets();
     .WithStaticAssets();
 
 
 app.MapControllers();
 app.MapControllers();
 
 
 app.Run();
 app.Run();
-

+ 1 - 2
EsimLao/Esim.Apis/appsettings.json

@@ -31,5 +31,4 @@
     "ClientSecret": "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",
     "ClientSecret": "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",
     "RedirectUri": "https://simgetgo.vn"
     "RedirectUri": "https://simgetgo.vn"
   }
   }
-
-}
+}

Файловите разлики са ограничени, защото са твърде много
+ 11 - 13
EsimLao/docs/.$skyhub.drawio.bkp


Файловите разлики са ограничени, защото са твърде много
+ 11 - 13
EsimLao/docs/skyhub.drawio


BIN
EsimLao/lib/DotnetLib.dll


Някои файлове не бяха показани, защото твърде много файлове са промени