ソースを参照

Merge branch 'master' of https://source.viettech.asia/unitel/ESIM

trunghieubui 2 週間 前
コミット
1e6f5c6aab

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

@@ -17,7 +17,6 @@ public static class CommonConstant
     public const string NextQuestionFail = "-8";
     public const string OtpWrong = "-9";
 
-
     public const string Unauthorized = "-401";
     public const string BadRequest = "-402";
 
@@ -29,7 +28,6 @@ public static class CommonConstant
     public const string ANSWER_RIGHT = "RIGHT";
     public const string ANSWER_WRONG = "WRONG";
 
-
     public const String PathImage = "/outside";
 
     public const String MissitonTypeLOGIN = "LOGIN";
@@ -37,7 +35,6 @@ public static class CommonConstant
     public const String MissitonTypeREGISTERS = "REGISTERS";
     public const String MissitonTypeINVITING = "INVITING";
 
-
     public const int StatusActive = 1;
     public const int StatusInactive = 0;
     public const int StatusNotClaimed = 2;
@@ -58,13 +55,11 @@ public static class CommonConstant
     public const string TypeInviting = "INVITING";
     public const string TypeRenewService = "RENEW";
 
-
     public const string PathOutside = "PathOutside";
 
     public const string CheckSub = "CHECKSUB";
     public const string Subscribe = "SUBSCRIBE";
 
-
     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> ListMissionType = new List<string>
@@ -83,9 +78,10 @@ public static class CommonConstant
         "REST_POST",
         "REST_GET"
     };
-    public static readonly List<string> ListServiceActionCode = new List<string> { 
+    public static readonly List<string> ListServiceActionCode = new List<string>
+    {
         "REGISTER",
-        "BUY_TURN", 
+        "BUY_TURN",
     };
 
     public static readonly List<string> ListVendorPackagePeriod = new List<string>
@@ -102,18 +98,9 @@ public static class CommonConstant
         "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 DateTimeFormar2 = "MMMM yyyy";
@@ -298,9 +285,5 @@ public static class CommonErrorCode
     public const string DuplicateEntry = "-804";
 
     // 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.Constant;
 using Common.Http;
 using Common.Logic;
-//using Esim.Apis.Logic;
-using Esim.Apis.Singleton;
 using Database;
 using Database.Database;
+//using Esim.Apis.Logic;
+using Esim.Apis.Singleton;
 using log4net;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.VisualBasic;
 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 System.Linq;
 
 namespace Esim.Apis.Business
 {
@@ -40,7 +40,6 @@ namespace Esim.Apis.Business
             return configuration.GetSection(key).Value ?? "";
         }
 
-
         /// <summary>
         /// Request OTP to be sent to email
         /// </summary>
@@ -68,8 +67,8 @@ namespace Esim.Apis.Business
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
 
                 // 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();
 
                 decimal? customerId = customer?.Id;
@@ -77,11 +76,14 @@ namespace Esim.Apis.Business
                 if (customer == null)
                 {
                     // 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 @)
                     string emailUsername = request.email.Split('@')[0];
-                    
+
                     var newCustomer = new CustomerInfo
                     {
                         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)
                 int otpExpireMinutes = 5;
                 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)
                     .FirstOrDefault();
 
                 if (existingOtp != null)
                 {
                     // 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)
                     {
-                        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(
                             log,
                             url,
                             json,
                             CommonErrorCode.OtpTooManyRequests,
                             $"Please wait {waitSeconds} seconds before requesting a new OTP.",
-                            new { 
+                            new
+                            {
                                 waitSeconds = waitSeconds,
-                                canRequestAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenRequests)
+                                canRequestAt = existingOtp.CreatedDate?.AddSeconds(
+                                    minSecondsBetweenRequests
+                                )
                             }
                         );
                     }
-                    
+
                     // UPDATE existing record - reuse for this email to prevent table bloat
                     existingOtp.OtpCode = otpCode;
                     existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                     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.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
                 {
                     // 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
                     {
                         Id = otpId,
@@ -171,8 +185,9 @@ namespace Esim.Apis.Business
                     string templateCode = "OTP_LOGIN";
 
                     // 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)
                     {
@@ -181,14 +196,20 @@ namespace Esim.Apis.Business
                     }
 
                     // 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)
-                    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
                     emailContent = emailContent
@@ -199,14 +220,15 @@ namespace Esim.Apis.Business
                     emailSubject = emailSubject
                         .Replace("{{OTP_CODE}}", otpCode)
                         .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
                     {
                         Id = emailMessageID,
                         MessageType = 1, // 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
                         Status = 0, // Pending
                         ScheduledAt = DateTime.Now,
@@ -220,7 +242,9 @@ namespace Esim.Apis.Business
                     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(
                 //    log,
@@ -241,11 +265,7 @@ namespace Esim.Apis.Business
                     json,
                     CommonErrorCode.Success,
                     ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
-                    new
-                    {
-                        email = request.email,
-                        expireInSeconds = otpExpireMinutes * 60
-                    }
+                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                 );
             }
             catch (Exception exception)
@@ -271,11 +291,11 @@ namespace Esim.Apis.Business
             var url = httpRequest.Path;
             var json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => ResendOtp Request: " + json);
-            
+
             try
             {
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
-                
+
                 // Validate email is required
                 if (string.IsNullOrEmpty(request.email))
                 {
@@ -290,8 +310,8 @@ namespace Esim.Apis.Business
                 }
 
                 // 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)
                     .FirstOrDefault();
 
@@ -311,22 +331,29 @@ namespace Esim.Apis.Business
 
                 // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
                 int minSecondsBetweenResend = 60;
-                var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
-                
+                var secondsSinceLastRequest = (
+                    DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
+                ).TotalSeconds;
+
                 if (secondsSinceLastRequest < minSecondsBetweenResend)
                 {
                     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(
                         log,
                         url,
                         json,
                         CommonErrorCode.OtpTooManyRequests,
                         $"Please wait {waitSeconds} seconds before resending OTP.",
-                        new { 
+                        new
+                        {
                             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)
                 bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
                 string otpCode = isTestAccount ? "111111" : GenerateOtp();
-                
+
                 // OTP expires in 5 minutes
                 int otpExpireMinutes = 5;
 
                 // 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();
                 decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
 
@@ -348,12 +375,12 @@ namespace Esim.Apis.Business
                 existingOtp.OtpCode = otpCode;
                 existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                 existingOtp.AttemptCount = 0; // Reset attempt count
-                existingOtp.IsUsed = false;   // Reset to unused
+                existingOtp.IsUsed = false; // Reset to unused
                 existingOtp.CustomerId = customerId;
                 existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
-                
+
                 log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
-                
+
                 await dbContext.SaveChangesAsync();
 
                 // Skip email sending for test account
@@ -361,9 +388,10 @@ namespace Esim.Apis.Business
                 {
                     // Add to MESSAGE_QUEUE for background email sending
                     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)
                     {
@@ -372,14 +400,20 @@ namespace Esim.Apis.Business
                     }
 
                     // 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
-                    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
                     emailContent = emailContent
@@ -390,7 +424,8 @@ namespace Esim.Apis.Business
                         .Replace("{{OTP_CODE}}", otpCode)
                         .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
                     {
                         Id = emailMessageID,
@@ -411,7 +446,9 @@ namespace Esim.Apis.Business
                     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(
                     log,
@@ -419,11 +456,7 @@ namespace Esim.Apis.Business
                     json,
                     CommonErrorCode.Success,
                     ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
-                    new
-                    {
-                        email = request.email,
-                        expireInSeconds = otpExpireMinutes * 60
-                    }
+                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                 );
             }
             catch (Exception exception)
@@ -467,19 +500,24 @@ namespace Esim.Apis.Business
                 string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
 
                 // 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)
                     .FirstOrDefault();
 
                 if (otpRecord == null)
                 {
                     // 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();
 
                     if (anyOtp != null)
@@ -491,7 +529,10 @@ namespace Esim.Apis.Business
                                 url,
                                 json,
                                 CommonErrorCode.OtpAlreadyUsed,
-                                ConfigManager.Instance.GetConfigWebValue("OTP_ALREADY_USED", responseLang),
+                                ConfigManager.Instance.GetConfigWebValue(
+                                    "OTP_ALREADY_USED",
+                                    responseLang
+                                ),
                                 new { }
                             );
                         }
@@ -502,7 +543,10 @@ namespace Esim.Apis.Business
                                 url,
                                 json,
                                 CommonErrorCode.OtpExpired,
-                                ConfigManager.Instance.GetConfigWebValue("OTP_EXPIRED", responseLang),
+                                ConfigManager.Instance.GetConfigWebValue(
+                                    "OTP_EXPIRED",
+                                    responseLang
+                                ),
                                 new { }
                             );
                         }
@@ -522,8 +566,8 @@ namespace Esim.Apis.Business
                 otpRecord.IsUsed = true;
 
                 // Get customer info
-                var customer = dbContext.CustomerInfos
-                    .Where(c => c.Email == request.email)
+                var customer = dbContext
+                    .CustomerInfos.Where(c => c.Email == request.email)
                     .FirstOrDefault();
 
                 if (customer == null)
@@ -547,14 +591,21 @@ namespace Esim.Apis.Business
                 int tokenExpireHours = 24;
                 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 refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
 
                 // 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();
 
                 foreach (var oldToken in oldTokens)
@@ -642,23 +693,26 @@ namespace Esim.Apis.Business
             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 json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
-            
-            try 
+
+            try
             {
                 string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
-                
+
                 var clientId = configuration["Google:ClientId"];
                 var redirectUri = configuration["Google:RedirectUri"];
-                
+
                 if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
                 {
                     log.Error("Google Auth configuration missing");
-                     return DotnetLib.Http.HttpResponse.BuildResponse(
+                    return DotnetLib.Http.HttpResponse.BuildResponse(
                         log,
                         url,
                         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&prompt=select_account";
-                
+                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(
-                        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)
             {
-                 log.Error("GoogleLogin Exception: ", ex);
-                 return DotnetLib.Http.HttpResponse.BuildResponse(
+                log.Error("GoogleLogin Exception: ", ex);
+                return DotnetLib.Http.HttpResponse.BuildResponse(
                     log,
                     url,
                     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 json = JsonConvert.SerializeObject(request);
             log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
-            
+
             try
             {
                 // Get language for response messages
                 string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
-                
+
                 if (string.IsNullOrEmpty(request.code))
                 {
-                     return DotnetLib.Http.HttpResponse.BuildResponse(
+                    return DotnetLib.Http.HttpResponse.BuildResponse(
                         log,
                         url,
                         json,
@@ -718,32 +776,42 @@ namespace Esim.Apis.Business
 
                 var clientId = configuration["Google:ClientId"];
                 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())
                 {
                     // 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();
-                    
+
                     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,
                             url,
                             json,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_TOKEN_EXCHANGE_FAILED", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_TOKEN_EXCHANGE_FAILED",
+                                lang
+                            ),
                             new { error = tokenResponseString }
                         );
                     }
@@ -753,30 +821,41 @@ namespace Esim.Apis.Business
 
                     if (string.IsNullOrEmpty(accessToken))
                     {
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             url,
                             json,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_NO_ACCESS_TOKEN", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_NO_ACCESS_TOKEN",
+                                lang
+                            ),
                             new { }
                         );
                     }
 
                     // 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();
 
                     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,
                             url,
                             json,
                             CommonErrorCode.ExternalServiceError,
-                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_USERINFO_FAILED", lang),
+                            ConfigManager.Instance.GetConfigWebValue(
+                                "GOOGLE_USERINFO_FAILED",
+                                lang
+                            ),
                             new { error = userInfoString }
                         );
                     }
@@ -785,10 +864,10 @@ namespace Esim.Apis.Business
                     var email = userInfo["email"]?.ToString();
                     var name = userInfo["name"]?.ToString(); // Full name
                     var picture = userInfo["picture"]?.ToString();
-                    
+
                     if (string.IsNullOrEmpty(email))
                     {
-                         return DotnetLib.Http.HttpResponse.BuildResponse(
+                        return DotnetLib.Http.HttpResponse.BuildResponse(
                             log,
                             url,
                             json,
@@ -799,14 +878,16 @@ namespace Esim.Apis.Business
                     }
 
                     // 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)
                     {
                         // 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
                         string surname = name;
                         string lastname = "";
@@ -819,9 +900,9 @@ namespace Esim.Apis.Business
                                 surname = string.Join(" ", parts.Take(parts.Length - 1));
                             }
                         }
-                        else 
+                        else
                         {
-                             surname = email.Split('@')[0];
+                            surname = email.Split('@')[0];
                         }
 
                         customer = new CustomerInfo
@@ -830,7 +911,7 @@ namespace Esim.Apis.Business
                             Email = email,
                             SurName = surname,
                             LastName = lastname,
-                            AvatarUrl = picture, 
+                            AvatarUrl = picture,
                             Status = true,
                             IsVerified = true, // Verified by Google
                             CreatedDate = DateTime.Now,
@@ -838,34 +919,48 @@ namespace Esim.Apis.Business
                         };
                         dbContext.CustomerInfos.Add(customer);
                         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
-                        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.LastLoginDate = DateTime.Now;
                         customer.LastUpdate = DateTime.Now;
                         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
                     int tokenExpireHours = 24;
                     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 refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
 
                     // 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();
 
                     foreach (var oldToken in oldTokens)
@@ -874,7 +969,8 @@ namespace Esim.Apis.Business
                     }
 
                     // 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
                     {
                         Id = tokenId,
@@ -916,7 +1012,7 @@ namespace Esim.Apis.Business
             catch (Exception exception)
             {
                 log.Error("GoogleCallback Exception: ", exception);
-                 return DotnetLib.Http.HttpResponse.BuildResponse(
+                return DotnetLib.Http.HttpResponse.BuildResponse(
                     log,
                     url,
                     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.Constant;
 using Common.Http;
-using Esim.Apis.Business;
 using Database.Database;
+using Esim.Apis.Business;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 
@@ -81,7 +81,10 @@ namespace RevoSystem.Apis.Controllers
         [Route(ApiUrlConstant.GoogleLoginUrl)]
         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>
@@ -97,6 +100,5 @@ namespace RevoSystem.Apis.Controllers
         {
             return await userBusiness.GoogleCallback(HttpContext.Request, request);
         }
-
     }
 }

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

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

+ 58 - 48
EsimLao/Esim.Apis/Program.cs

@@ -2,33 +2,38 @@ using System.Text;
 using Common.Global;
 using Database.Database;
 using Esim.Apis.Business;
+using log4net;
+using log4net.Config;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.IdentityModel.Tokens;
 
-using log4net;
-using log4net.Config;
-
 var builder = WebApplication.CreateBuilder(args);
 
 // 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
 GlobalConfig.Configuration = builder.Configuration;
 
 // Add services to the container.
-builder.Services.AddControllersWithViews()
+builder
+    .Services.AddControllersWithViews()
     .AddJsonOptions(options =>
     {
         // 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
 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
 builder.Services.AddScoped<IUserBusiness, UserBusinessImpl>();
@@ -38,48 +43,55 @@ builder.Services.AddScoped<IContentBusiness, ContentBusinessImpl>();
 // Configure CORS - Allow frontend to access APIs
 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://skysimhub.vn",    // Production www domain
-                "https://skysimhub.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://skysimhub.vn", // Production www domain
+                    "https://skysimhub.com", // Production www domain
+                    "https://simgetgo.vn", // Production www domain
+                    "https://simgetgo.com" // Production www domain
+                )
+                .AllowAnyMethod()
+                .AllowAnyHeader()
+                .AllowCredentials();
+        }
+    );
 });
 
 // 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 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
 builder.Services.AddEndpointsApiExplorer();
@@ -96,7 +108,7 @@ using (var scope = app.Services.CreateScope())
         logger.LogInformation("Initializing ConfigManager...");
         Esim.Apis.Singleton.ConfigManager.Instance.Initialize();
         logger.LogInformation("ConfigManager initialized successfully");
-        
+
         // Start background refresh task
         Task.Run(() => Esim.Apis.Singleton.ConfigManager.Instance.RefreshConfigs());
     }
@@ -107,6 +119,7 @@ using (var scope = app.Services.CreateScope())
 }
 app.UseSwagger();
 app.UseSwaggerUI();
+
 // Configure the HTTP request pipeline.
 //if (app.Environment.IsDevelopment())
 //{
@@ -136,12 +149,9 @@ app.UseAuthorization();
 
 app.MapStaticAssets();
 
-app.MapControllerRoute(
-    name: "default",
-    pattern: "{controller=Home}/{action=Index}/{id?}")
+app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}")
     .WithStaticAssets();
 
 app.MapControllers();
 
 app.Run();
-

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

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

ファイルの差分が大きいため隠しています
+ 11 - 13
EsimLao/docs/.$skyhub.drawio.bkp


ファイルの差分が大きいため隠しています
+ 6 - 8
EsimLao/docs/skyhub.drawio


BIN
EsimLao/lib/DotnetLib.dll


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません