diff --git a/SPMS.API/Controllers/AuthController.cs b/SPMS.API/Controllers/AuthController.cs index b4080d9..026d674 100644 --- a/SPMS.API/Controllers/AuthController.cs +++ b/SPMS.API/Controllers/AuthController.cs @@ -24,9 +24,9 @@ public class AuthController : ControllerBase [AllowAnonymous] [SwaggerOperation( Summary = "회원가입", - Description = "새로운 관리자 계정을 생성합니다. 이메일 인증이 필요합니다.")] + Description = "새로운 관리자 계정을 생성합니다. 약관/개인정보 동의(agreeTerms, agreePrivacy)가 필수이며, 성공 시 이메일 인증 세션(verifySessionId)과 메일 발송 결과(emailSent)를 반환합니다.")] [SwaggerResponse(200, "회원가입 성공", typeof(ApiResponse))] - [SwaggerResponse(400, "잘못된 요청")] + [SwaggerResponse(400, "잘못된 요청 또는 동의 미체크")] [SwaggerResponse(409, "이미 사용 중인 이메일")] public async Task SignupAsync([FromBody] SignupRequestDto request) { diff --git a/SPMS.Application/DTOs/Auth/SignupRequestDto.cs b/SPMS.Application/DTOs/Auth/SignupRequestDto.cs index 0e9494b..9f9e0ab 100644 --- a/SPMS.Application/DTOs/Auth/SignupRequestDto.cs +++ b/SPMS.Application/DTOs/Auth/SignupRequestDto.cs @@ -19,4 +19,10 @@ public class SignupRequestDto [Required(ErrorMessage = "전화번호는 필수입니다.")] [StringLength(20, ErrorMessage = "전화번호는 20자 이내여야 합니다.")] public string Phone { get; set; } = string.Empty; + + [Required(ErrorMessage = "서비스 이용약관 동의는 필수입니다.")] + public bool AgreeTerms { get; set; } + + [Required(ErrorMessage = "개인정보 처리방침 동의는 필수입니다.")] + public bool AgreePrivacy { get; set; } } diff --git a/SPMS.Application/DTOs/Auth/SignupResponseDto.cs b/SPMS.Application/DTOs/Auth/SignupResponseDto.cs index dade51a..c9fcdfb 100644 --- a/SPMS.Application/DTOs/Auth/SignupResponseDto.cs +++ b/SPMS.Application/DTOs/Auth/SignupResponseDto.cs @@ -9,4 +9,10 @@ public class SignupResponseDto [JsonPropertyName("email")] public string Email { get; set; } = string.Empty; + + [JsonPropertyName("verify_session_id")] + public string? VerifySessionId { get; set; } + + [JsonPropertyName("email_sent")] + public bool EmailSent { get; set; } } diff --git a/SPMS.Application/Services/AuthService.cs b/SPMS.Application/Services/AuthService.cs index 90d43b8..b08e535 100644 --- a/SPMS.Application/Services/AuthService.cs +++ b/SPMS.Application/Services/AuthService.cs @@ -38,7 +38,13 @@ public class AuthService : IAuthService public async Task SignupAsync(SignupRequestDto request) { - // 1. 이메일 중복 검사 + // 1. 동의 검증 + if (!request.AgreeTerms) + throw new SpmsException(ErrorCodes.TermsNotAgreed, "서비스 이용약관에 동의해야 합니다.", 400); + if (!request.AgreePrivacy) + throw new SpmsException(ErrorCodes.PrivacyNotAgreed, "개인정보 처리방침에 동의해야 합니다.", 400); + + // 2. 이메일 중복 검사 if (await _adminRepository.EmailExistsAsync(request.Email)) { throw new SpmsException( @@ -47,10 +53,10 @@ public class AuthService : IAuthService 409); } - // 2. AdminCode 생성 (UUID 12자) + // 3. AdminCode 생성 (UUID 12자) var adminCode = Guid.NewGuid().ToString("N")[..12].ToUpper(); - // 3. Admin 엔티티 생성 + // 4. Admin 엔티티 생성 (동의 필드 포함) var admin = new Admin { AdminCode = adminCode, @@ -61,26 +67,48 @@ public class AuthService : IAuthService Role = AdminRole.User, EmailVerified = false, CreatedAt = DateTime.UtcNow, - IsDeleted = false + IsDeleted = false, + AgreeTerms = true, + AgreePrivacy = true, + AgreedAt = DateTime.UtcNow }; - // 4. 저장 + // 5. 저장 await _adminRepository.AddAsync(admin); await _unitOfWork.SaveChangesAsync(); - // 5. 이메일 인증 코드 생성/저장/발송 + // 6. 이메일 인증 코드 생성/저장 var verificationCode = Random.Shared.Next(100000, 999999).ToString(); await _tokenStore.StoreAsync( $"email_verify:{request.Email}", verificationCode, TimeSpan.FromHours(1)); - await _emailService.SendVerificationCodeAsync(request.Email, verificationCode); - // 6. 응답 반환 + // 7. Verify Session 생성 (sessionId → email 매핑) + var verifySessionId = Guid.NewGuid().ToString("N"); + await _tokenStore.StoreAsync( + $"verify_session:{verifySessionId}", + request.Email, + TimeSpan.FromHours(1)); + + // 8. 메일 발송 (실패해도 가입은 유지) + var emailSent = true; + try + { + await _emailService.SendVerificationCodeAsync(request.Email, verificationCode); + } + catch + { + emailSent = false; + } + + // 9. 응답 반환 return new SignupResponseDto { AdminCode = admin.AdminCode, - Email = admin.Email + Email = admin.Email, + VerifySessionId = verifySessionId, + EmailSent = emailSent }; } diff --git a/SPMS.Domain/Common/ErrorCodes.cs b/SPMS.Domain/Common/ErrorCodes.cs index 41438ce..3e8e2a9 100644 --- a/SPMS.Domain/Common/ErrorCodes.cs +++ b/SPMS.Domain/Common/ErrorCodes.cs @@ -23,6 +23,8 @@ public static class ErrorCodes public const string VerificationCodeError = "111"; public const string LoginFailed = "112"; public const string LoginAttemptExceeded = "113"; + public const string TermsNotAgreed = "114"; + public const string PrivacyNotAgreed = "115"; // === Account (2) === public const string PasswordValidationFailed = "121"; diff --git a/SPMS.Domain/Entities/Admin.cs b/SPMS.Domain/Entities/Admin.cs index 16e548e..5e7a48c 100644 --- a/SPMS.Domain/Entities/Admin.cs +++ b/SPMS.Domain/Entities/Admin.cs @@ -18,4 +18,7 @@ public class Admin : BaseEntity public DateTime? RefreshTokenExpiresAt { get; set; } public bool IsDeleted { get; set; } public DateTime? DeletedAt { get; set; } + public bool AgreeTerms { get; set; } + public bool AgreePrivacy { get; set; } + public DateTime AgreedAt { get; set; } } diff --git a/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.Designer.cs b/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.Designer.cs new file mode 100644 index 0000000..75bc630 --- /dev/null +++ b/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.Designer.cs @@ -0,0 +1,1111 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SPMS.Infrastructure; + +#nullable disable + +namespace SPMS.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260225002821_AddConsentFieldsToAdmin")] + partial class AddConsentFieldsToAdmin + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("SPMS.Domain.Entities.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdminCode") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("varchar(8)"); + + b.Property("AgreePrivacy") + .HasColumnType("tinyint(1)"); + + b.Property("AgreeTerms") + .HasColumnType("tinyint(1)"); + + b.Property("AgreedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EmailVerified") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("EmailVerifiedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("RefreshToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RefreshTokenExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Role") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("AdminCode") + .IsUnique(); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Admin", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.AppConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ConfigKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ConfigValue") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId", "ConfigKey") + .IsUnique(); + + b.ToTable("AppConfig", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Banner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("LinkType") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Position") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.ToTable("Banner", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.DailyStat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FailCnt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("OpenCnt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("SentCnt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("StatDate") + .HasColumnType("date"); + + b.Property("SuccessCnt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("ServiceId", "StatDate") + .IsUnique(); + + b.ToTable("DailyStat", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AgreeUpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("AppVersion") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeviceModel") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("MarketingAgreed") + .HasColumnType("tinyint(1)"); + + b.Property("MktAgreeUpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("OsVersion") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("Platform") + .HasColumnType("tinyint"); + + b.Property("PushAgreed") + .HasColumnType("tinyint(1)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Tags") + .HasColumnType("json"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId", "DeviceToken"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Faq", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .IsRequired() + .HasColumnType("text"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("Question") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.ToTable("Faq", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("MimeType") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ServiceId"); + + b.ToTable("File", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Body") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("CustomData") + .HasColumnType("json"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("ImageUrl") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("LinkType") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("MessageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("MessageCode") + .IsUnique(); + + b.HasIndex("ServiceId"); + + b.ToTable("Message", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Notice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("IsPinned") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ServiceId"); + + b.ToTable("Notice", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("bigint"); + + b.Property("Amount") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("PaidAt") + .HasColumnType("datetime(6)"); + + b.Property("PaymentKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PaymentMethod") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("TierAfter") + .HasColumnType("tinyint"); + + b.Property("TierBefore") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.HasIndex("ServiceId"); + + b.ToTable("Payment", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.PushOpenLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DeviceId") + .HasColumnType("bigint"); + + b.Property("MessageId") + .HasColumnType("bigint"); + + b.Property("OpenedAt") + .HasColumnType("datetime(6)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("MessageId"); + + b.HasIndex("ServiceId", "OpenedAt"); + + b.ToTable("PushOpenLog", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.PushSendLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DeviceId") + .HasColumnType("bigint"); + + b.Property("FailReason") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("MessageId") + .HasColumnType("bigint"); + + b.Property("SentAt") + .HasColumnType("datetime(6)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("MessageId"); + + b.HasIndex("ServiceId", "SentAt"); + + b.ToTable("PushSendLog", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Service", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("ApiKeyCreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ApnsBundleId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ApnsKeyId") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("ApnsPrivateKey") + .HasColumnType("text"); + + b.Property("ApnsTeamId") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("bigint"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("FcmCredentials") + .HasColumnType("text"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("ServiceCode") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("varchar(8)"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("SubStartedAt") + .HasColumnType("datetime(6)"); + + b.Property("SubTier") + .HasColumnType("tinyint"); + + b.Property("Tags") + .HasColumnType("json"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WebhookEvents") + .HasColumnType("longtext"); + + b.Property("WebhookUrl") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ServiceCode") + .IsUnique(); + + b.ToTable("Service", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.ServiceIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("varchar(45)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.ToTable("ServiceIp", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.SystemLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("AdminId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasColumnType("json"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("varchar(45)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("TargetId") + .HasColumnType("bigint"); + + b.Property("TargetType") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ServiceId"); + + b.ToTable("SystemLog", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("EventType") + .HasColumnType("tinyint"); + + b.Property("Payload") + .IsRequired() + .HasColumnType("json"); + + b.Property("ResponseBody") + .HasColumnType("text"); + + b.Property("ResponseCode") + .HasColumnType("int"); + + b.Property("SentAt") + .HasColumnType("datetime(6)"); + + b.Property("ServiceId") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("WebhookUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId", "SentAt"); + + b.ToTable("WebhookLog", (string)null); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.AppConfig", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Banner", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.DailyStat", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Device", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany("Devices") + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Faq", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.FileEntity", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "CreatedByAdmin") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByAdmin"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Message", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "CreatedByAdmin") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany("Messages") + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByAdmin"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Notice", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "CreatedByAdmin") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByAdmin"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Payment", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "Admin") + .WithMany() + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Admin"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.PushOpenLog", b => + { + b.HasOne("SPMS.Domain.Entities.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Message", "Message") + .WithMany() + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("Message"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.PushSendLog", b => + { + b.HasOne("SPMS.Domain.Entities.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Message", "Message") + .WithMany() + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("Message"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Service", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "CreatedByAdmin") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByAdmin"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.ServiceIp", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany("ServiceIps") + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.SystemLog", b => + { + b.HasOne("SPMS.Domain.Entities.Admin", "Admin") + .WithMany() + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Admin"); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b => + { + b.HasOne("SPMS.Domain.Entities.Service", "Service") + .WithMany() + .HasForeignKey("ServiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Service"); + }); + + modelBuilder.Entity("SPMS.Domain.Entities.Service", b => + { + b.Navigation("Devices"); + + b.Navigation("Messages"); + + b.Navigation("ServiceIps"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.cs b/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.cs new file mode 100644 index 0000000..365ea2d --- /dev/null +++ b/SPMS.Infrastructure/Migrations/20260225002821_AddConsentFieldsToAdmin.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SPMS.Infrastructure.Migrations +{ + /// + public partial class AddConsentFieldsToAdmin : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AgreePrivacy", + table: "Admin", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "AgreeTerms", + table: "Admin", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "AgreedAt", + table: "Admin", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AgreePrivacy", + table: "Admin"); + + migrationBuilder.DropColumn( + name: "AgreeTerms", + table: "Admin"); + + migrationBuilder.DropColumn( + name: "AgreedAt", + table: "Admin"); + } + } +} diff --git a/SPMS.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/SPMS.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 59fdbc5..62baa09 100644 --- a/SPMS.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/SPMS.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -35,6 +35,15 @@ namespace SPMS.Infrastructure.Migrations .HasMaxLength(8) .HasColumnType("varchar(8)"); + b.Property("AgreePrivacy") + .HasColumnType("tinyint(1)"); + + b.Property("AgreeTerms") + .HasColumnType("tinyint(1)"); + + b.Property("AgreedAt") + .HasColumnType("datetime(6)"); + b.Property("CreatedAt") .HasColumnType("datetime(6)"); diff --git a/SPMS.Infrastructure/Persistence/Configurations/AdminConfiguration.cs b/SPMS.Infrastructure/Persistence/Configurations/AdminConfiguration.cs index 289a76c..e2b0cac 100644 --- a/SPMS.Infrastructure/Persistence/Configurations/AdminConfiguration.cs +++ b/SPMS.Infrastructure/Persistence/Configurations/AdminConfiguration.cs @@ -31,6 +31,9 @@ public class AdminConfiguration : IEntityTypeConfiguration builder.Property(e => e.RefreshTokenExpiresAt); builder.Property(e => e.IsDeleted).HasColumnType("tinyint(1)").IsRequired().HasDefaultValue(false); builder.Property(e => e.DeletedAt); + builder.Property(e => e.AgreeTerms).HasColumnType("tinyint(1)").IsRequired(); + builder.Property(e => e.AgreePrivacy).HasColumnType("tinyint(1)").IsRequired(); + builder.Property(e => e.AgreedAt).IsRequired(); builder.HasQueryFilter(e => !e.IsDeleted); }