using Microsoft.AspNetCore.Http; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Application.Features.Config.Update; public sealed class Request { public int ID { get; init; } public BasicConfigDto Basic { get; init; } = new(); public ImagesConfigDto Images { get; init; } = new(); public MetaConfigDto Meta { get; init; } = new(); public CompanyConfigDto Company { get; init; } = new(); public AccountConfigDto Account { get; init; } = new(); public EmailTemplateConfigDto EmailTemplate { get; init; } = new(); public ExternalApiConfigDto External { get; init; } = new(); public TossConfigDto Toss { get; init; } = new(); public AttendanceConfigDto? Attendance { get; init; } public SignupRewardConfigDto? SignupReward { get; init; } // 기본 설정 public sealed class BasicConfigDto { [MaxLength(100)] [DisplayName("사이트 이름")] public string? SiteName { get; init; } [MaxLength(100)] [DataType(DataType.Url)] [DisplayName("사이트 주소")] public string? SiteURL { get; init; } [DisplayName("최고 관리자 ID")] public string? RootID { get; init; } [MaxLength(100)] [DataType(DataType.EmailAddress)] [DisplayName("발송자 이메일")] public string? FromEmail { get; init; } [MaxLength(30)] [DisplayName("발송자 이름")] public string? FromName { get; init; } [MaxLength(200)] [DisplayName("SMTP Server")] public string? SmtpServer { get; init; } [Range(1, 65535)] [DisplayName("SMTP Port")] public int? SmtpPort { get; set; } [DisplayName("SMTP Enable SSL")] public bool SmtpEnableSSL { get; init; } = false; [MaxLength(100)] [DisplayName("SMTP Username")] public string? SmtpUsername { get; init; } [MaxLength(200)] [DataType(DataType.Password)] [DisplayName("SMTP Password")] public string? SmtpPassword { get; init; } [MaxLength(200)] [DataType(DataType.MultilineText)] [DisplayName("관리자단 접근 가능 IP")] public string? AdminWhiteIPList { get; init; } [MaxLength(200)] [DataType(DataType.MultilineText)] [DisplayName("사용자단 접근 가능 IP")] public string? FrontWhiteIPList { get; init; } [MaxLength(200)] [DisplayName("차단 시 안내문 제목")] public string? BlockAlertTitle { get; init; } [MaxLength(5000)] [DataType(DataType.MultilineText)] [DisplayName("차단 시 안내문 내용")] public string? BlockAlertContent { get; init; } [DisplayName("점검 여부")] public bool IsMaintenance { get; init; } = false; [MaxLength(5000)] [DataType(DataType.MultilineText)] [DisplayName("점검 내용")] public string? MaintenanceContent { get; init; } [Range(0, 100)] [DisplayName("쪽지 일일 발송 제한")] public int NoteDailySendLimit { get; init; } = 3; } // 이미지 경로 및 업로드 입력 public sealed class ImagesConfigDto { // ====== DB에 저장/표기할 경로(문자열) ====== [MaxLength(255)] [DisplayName("Favicon")] public string? FaviconPath { get; init; } [MaxLength(255)] [DisplayName("Logo-square")] public string? LogoSquarePath { get; init; } [MaxLength(255)] [DisplayName("Logo-horizontal")] public string? LogoHorizontalPath { get; init; } [MaxLength(255)] [DisplayName("og-default")] public string? OgDefaultPath { get; init; } [MaxLength(255)] [DisplayName("Twitter-image")] public string? TwitterImagePath { get; init; } [MaxLength(255)] [DisplayName("Apple-touch-icon")] public string? AppleTouchIconPath { get; init; } [MaxLength(255)] [DisplayName("App-icon-192")] public string? AppIcon192Path { get; init; } [MaxLength(255)] [DisplayName("App-icon-512")] public string? AppIcon512Path { get; init; } // ====== 업로드 입력(새 업로드용) ====== [DataType(DataType.Upload)] [DisplayName("Favicon 업로드")] public IFormFile? FaviconFile { get; init; } [DataType(DataType.Upload)] [DisplayName("Logo-square 업로드")] public IFormFile? LogoSquareFile { get; init; } [DataType(DataType.Upload)] [DisplayName("Logo-horizontal 업로드")] public IFormFile? LogoHorizontalFile { get; init; } [DataType(DataType.Upload)] [DisplayName("og-default 업로드")] public IFormFile? OgDefaultFile { get; init; } [DataType(DataType.Upload)] [DisplayName("Twitter-image 업로드")] public IFormFile? TwitterImageFile { get; init; } [DataType(DataType.Upload)] [DisplayName("Apple-touch-icon 업로드")] public IFormFile? AppleTouchIconFile { get; init; } [DataType(DataType.Upload)] [DisplayName("App-icon-192 업로드")] public IFormFile? AppIcon192File { get; init; } [DataType(DataType.Upload)] [DisplayName("App-icon-512 업로드")] public IFormFile? AppIcon512File { get; init; } } // 메타 태그 public sealed class MetaConfigDto { [MaxLength(255)] [DisplayName("Meta Keywords")] public string? Keywords { get; init; } [MaxLength(255)] [DisplayName("Meta Description")] public string? Description { get; init; } [MaxLength(255)] [DisplayName("Meta Author")] public string? Author { get; init; } [MaxLength(255)] [DisplayName("Meta Viewport")] public string? Viewport { get; init; } [MaxLength(255)] [DisplayName("Meta ApplicationName")] public string? ApplicationName { get; init; } [MaxLength(255)] [DisplayName("Meta Generator")] public string? Generator { get; init; } [MaxLength(255)] [DisplayName("Meta Robots")] public string? Robots { get; init; } [MaxLength(3000)] [DataType(DataType.MultilineText)] [DisplayName("Meta Adds")] public string? Adds { get; init; } } // 회사 정보 public sealed class CompanyConfigDto { [MaxLength(70)] [DisplayName("상호 명")] public string? Name { get; init; } [MaxLength(100)] [DisplayName("사업자 등록 번호")] public string? RegNo { get; init; } [MaxLength(255)] [DisplayName("사업장 소재지")] public string? Address { get; init; } [MaxLength(8)] [DataType(DataType.PostalCode)] [DisplayName("우편번호")] public string? ZipCode { get; init; } [MaxLength(50)] [DisplayName("대표자 명")] public string? Owner { get; init; } [MaxLength(20)] [DisplayName("대표 전화번호")] public string? Tel { get; init; } [MaxLength(20)] [DisplayName("FAX")] public string? Fax { get; init; } [MaxLength(20)] [DisplayName("통신판매업 신고번호")] public string? RetailSaleNo { get; init; } [MaxLength(20)] [DisplayName("부가통신 사업자번호")] public string? AddedSaleNo { get; init; } [MaxLength(100)] [DisplayName("호스팅 서비스")] public string? Hosting { get; init; } [MaxLength(70)] [DisplayName("정보관리책임자")] public string? AdminName { get; init; } [MaxLength(100)] [DataType(DataType.EmailAddress)] [DisplayName("정보관리책임자 이메일")] public string? AdminEmail { get; init; } [MaxLength(200)] [DataType(DataType.Url)] [DisplayName("사이트 주소")] public string? SiteUrl { get; init; } [MaxLength(10)] [DisplayName("입금계좌 - 은행")] public string? BankCode { get; init; } [MaxLength(70)] [DisplayName("입금계좌 - 예금주")] public string? BankOwner { get; init; } [MaxLength(100)] [DisplayName("입금계좌 - 계좌번호")] public string? BankNumber { get; init; } } // 회원 설정 public sealed class AccountConfigDto { [DisplayName("회원가입 시 - 가입 차단")] public bool IsRegisterBlock { get; init; } = false; [DisplayName("회원가입 시 - 이메일 인증 여부")] public bool IsRegisterEmailAuth { get; init; } = false; [Range(0, 100)] [DisplayName("회원가입 시 - 비밀번호 최소 길이")] public ushort? PasswordMinLength { get; init; } [Range(0, 100)] [DisplayName("회원가입 시 - 비밀번호 대문자 최소 길이")] public ushort? PasswordUppercaseLength { get; init; } [Range(0, 100)] [DisplayName("회원가입 시 - 비밀번호 숫자 최소 길이")] public ushort? PasswordNumbersLength { get; init; } [Range(0, 100)] [DisplayName("회원가입 시 - 비밀번호 특수문자 최소 길이")] public ushort? PasswordSpecialcharsLength { get; init; } [MaxLength(4000)] [DisplayName("회원가입 시 - 금지 이메일")] public string? DeniedEmailList { get; init; } [MaxLength(4000)] [DisplayName("회원가입 시 - 금지 별명")] public string? DeniedNameList { get; init; } [Range(0, 365)] [DisplayName("회원가입 시 - 이메일 갱신 주기")] public ushort? ChangeEmailDay { get; init; } [Range(0, 365)] [DisplayName("회원가입 시 - 별명 갱신 주기")] public ushort? ChangeNameDay { get; init; } [Range(0, 365)] [DisplayName("회원가입 시 - 한마디 갱신 주기")] public ushort? ChangeSummaryDay { get; init; } [Range(0, 365)] [DisplayName("회원가입 시 - 자기소개 갱신 주기")] public ushort? ChangeIntroDay { get; init; } [Range(0, 365)] [DisplayName("회원가입 시 - 비밀번호 갱신 주기")] public ushort? ChangePasswordDay { get; init; } [DisplayName("로그인 시 - 이메일 인증 필요")] public bool IsLoginEmailVerifiedOnly { get; init; } = false; [Range(0, 100)] [DisplayName("로그인 시 - 로그인 시도(회)")] public ushort? MaxLoginTryCount { get; init; } [Range(0, 86400)] [DisplayName("로그인 시 - 로그인 제한(초)")] public ushort? MaxLoginTryLimitSecond { get; init; } } // 알림 발송 양식 public sealed class EmailTemplateConfigDto { [MaxLength(4000)] [DisplayName("회원가입 시 - 제목")] public string? RegisterEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("회원가입 시 - 내용")] public string? RegisterEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("회원가입 완료 - 제목")] public string? RegistrationEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("회원가입 완료 - 내용")] public string? RegistrationEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("비밀번호 재설정 - 제목")] public string? ResetPasswordEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("비밀번호 재설정 - 내용")] public string? ResetPasswordEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("비밀번호 변경 완료 - 제목")] public string? ChangedPasswordEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("비밀번호 변경 완료 - 내용")] public string? ChangedPasswordEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("회원탈퇴 시 - 제목")] public string? WithdrawEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("회원탈퇴 시 - 내용")] public string? WithdrawEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("회원탈퇴 인증 - 제목")] public string? WithdrawVerifyEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("회원탈퇴 인증 - 내용")] public string? WithdrawVerifyEmailFormContent { get; init; } [MaxLength(4000)] [DisplayName("이메일 변경 시 - 제목")] public string? EmailVerifyFormTitle { get; init; } [MaxLength(4000)] [DisplayName("이메일 변경 시 - 내용")] public string? EmailVerifyFormContent { get; init; } [MaxLength(4000)] [DisplayName("이메일 변경 완료 - 제목")] public string? ChangedEmailFormTitle { get; init; } [MaxLength(4000)] [DisplayName("이메일 변경 완료 - 내용")] public string? ChangedEmailFormContent { get; init; } } // API 정보 public sealed class ExternalApiConfigDto { [MaxLength(500)] [DisplayName("YouTube - API Name")] public string? YouTubeApiKeyEnc { get; init; } [MaxLength(500)] [DisplayName("YouTube - API Key")] public string? YouTubeApiName { get; init; } [MaxLength(500)] [DisplayName("Google - Client ID")] public string? GoogleClientId { get; init; } [MaxLength(500)] [DisplayName("Google - Client Secret")] public string? GoogleClientSecretEnc { get; init; } [MaxLength(500)] [DisplayName("Google - App ID")] public string? GoogleAppId { get; init; } } // 토스페이먼츠 결제 설정 (결제 설정 화면 전용) // 키 입력값은 저장 시 암호화되며, 빈 값으로 제출하면 기존 저장 키를 유지한다. public sealed class TossConfigDto { [DisplayName("토스 - 결제 환경")] public string? TossPayMode { get; init; } [MaxLength(500)] [DisplayName("토스 - Test Client Key")] public string? TestClientKey { get; init; } [MaxLength(500)] [DisplayName("토스 - Test Secret Key")] public string? TestSecretKey { get; init; } [MaxLength(500)] [DisplayName("토스 - Live Client Key")] public string? LiveClientKey { get; init; } [MaxLength(500)] [DisplayName("토스 - Live Secret Key")] public string? LiveSecretKey { get; init; } } // 출석 설정 public sealed class AttendanceConfigDto { [DisplayName("출석 기능 활성화")] public bool IsEnabled { get; init; } = false; [Range(0, 100000)] [DisplayName("기본 경험치")] public int BaseExp { get; init; } = 0; [Range(0, 100000)] [DisplayName("기본 코인")] public int BasePoint { get; init; } = 0; [DisplayName("연속 출석 가중치 사용")] public bool UseStreakBonus { get; init; } = false; [Range(0, 10000)] [DisplayName("1일당 추가 경험치")] public int StreakBonusPerDay { get; init; } = 0; [Range(0, 10000)] [DisplayName("1일당 추가 코인")] public int StreakBonusPointPerDay { get; init; } = 0; [Range(0, 365)] [DisplayName("가중치 최대 적용 일수")] public int StreakBonusMaxDays { get; init; } = 0; [DisplayName("순위 보상 사용")] public bool UseRankBonus { get; init; } = false; [MaxLength(2000)] [DisplayName("순위별 보상 설정 (JSON)")] public string? RankBonusConfig { get; init; } } // 가입 축하 보상 설정 public sealed class SignupRewardConfigDto { [DisplayName("가입 축하 보상 활성화")] public bool Enabled { get; init; } = false; [Range(0, 100000000)] [DisplayName("지급 코인")] public int CoinAmount { get; init; } = 0; [Range(0, 100000000)] [DisplayName("지급 캐시")] public int CashAmount { get; init; } = 0; [Range(0, 100000)] [DisplayName("지급 경험치")] public int ExpAmount { get; init; } = 0; } public static Request From(Get.Response src) => new() { ID = src.ID, Basic = new BasicConfigDto { SiteName = src.Basic.SiteName, SiteURL = src.Basic.SiteURL, RootID = src.Basic.RootID, FromEmail = src.Basic.FromEmail, FromName = src.Basic.FromName, SmtpServer = src.Basic.SmtpServer, SmtpPort = src.Basic.SmtpPort, SmtpEnableSSL = src.Basic.SmtpEnableSSL, SmtpUsername = src.Basic.SmtpUsername, SmtpPassword = src.Basic.SmtpPassword, AdminWhiteIPList = src.Basic.AdminWhiteIPList, FrontWhiteIPList = src.Basic.FrontWhiteIPList, BlockAlertTitle = src.Basic.BlockAlertTitle, BlockAlertContent = src.Basic.BlockAlertContent, IsMaintenance = src.Basic.IsMaintenance, MaintenanceContent = src.Basic.MaintenanceContent, NoteDailySendLimit = src.Basic.NoteDailySendLimit }, Images = new ImagesConfigDto { FaviconPath = src.Images.FaviconPath, LogoSquarePath = src.Images.LogoSquarePath, LogoHorizontalPath = src.Images.LogoHorizontalPath, OgDefaultPath = src.Images.OgDefaultPath, TwitterImagePath = src.Images.TwitterImagePath, AppleTouchIconPath = src.Images.AppleTouchIconPath, AppIcon192Path = src.Images.AppIcon192Path, AppIcon512Path = src.Images.AppIcon512Path }, Meta = new MetaConfigDto { Keywords = src.Meta.Keywords, Description = src.Meta.Description, Author = src.Meta.Author, Viewport = src.Meta.Viewport, ApplicationName = src.Meta.ApplicationName, Generator = src.Meta.Generator, Robots = src.Meta.Robots, Adds = src.Meta.Adds }, Company = new CompanyConfigDto { Name = src.Company.Name, RegNo = src.Company.RegNo, Address = src.Company.Address, ZipCode = src.Company.ZipCode, Owner = src.Company.Owner, Tel = src.Company.Tel, Fax = src.Company.Fax, RetailSaleNo = src.Company.RetailSaleNo, AddedSaleNo = src.Company.AddedSaleNo, Hosting = src.Company.Hosting, AdminName = src.Company.AdminName, AdminEmail = src.Company.AdminEmail, SiteUrl = src.Company.SiteUrl, BankCode = src.Company.BankCode, BankOwner = src.Company.BankOwner, BankNumber = src.Company.BankNumber }, Account = new AccountConfigDto { IsRegisterBlock = src.Account.IsRegisterBlock, IsRegisterEmailAuth = src.Account.IsRegisterEmailAuth, PasswordMinLength = src.Account.PasswordMinLength, PasswordUppercaseLength = src.Account.PasswordUppercaseLength, PasswordNumbersLength = src.Account.PasswordNumbersLength, PasswordSpecialcharsLength = src.Account.PasswordSpecialcharsLength, DeniedEmailList = src.Account.DeniedEmailList, DeniedNameList = src.Account.DeniedNameList, ChangeEmailDay = src.Account.ChangeEmailDay, ChangeNameDay = src.Account.ChangeNameDay, ChangeSummaryDay = src.Account.ChangeSummaryDay, ChangeIntroDay = src.Account.ChangeIntroDay, ChangePasswordDay = src.Account.ChangePasswordDay, IsLoginEmailVerifiedOnly = src.Account.IsLoginEmailVerifiedOnly, MaxLoginTryCount = src.Account.MaxLoginTryCount, MaxLoginTryLimitSecond = src.Account.MaxLoginTryLimitSecond }, EmailTemplate = new EmailTemplateConfigDto { RegisterEmailFormTitle = src.EmailTemplate.RegisterEmailFormTitle, RegisterEmailFormContent = src.EmailTemplate.RegisterEmailFormContent, RegistrationEmailFormTitle = src.EmailTemplate.RegistrationEmailFormTitle, RegistrationEmailFormContent = src.EmailTemplate.RegistrationEmailFormContent, ResetPasswordEmailFormTitle = src.EmailTemplate.ResetPasswordEmailFormTitle, ResetPasswordEmailFormContent = src.EmailTemplate.ResetPasswordEmailFormContent, ChangedPasswordEmailFormTitle = src.EmailTemplate.ChangedPasswordEmailFormTitle, ChangedPasswordEmailFormContent = src.EmailTemplate.ChangedPasswordEmailFormContent, WithdrawEmailFormTitle = src.EmailTemplate.WithdrawEmailFormTitle, WithdrawEmailFormContent = src.EmailTemplate.WithdrawEmailFormContent, WithdrawVerifyEmailFormTitle = src.EmailTemplate.WithdrawVerifyEmailFormTitle, WithdrawVerifyEmailFormContent = src.EmailTemplate.WithdrawVerifyEmailFormContent, EmailVerifyFormTitle = src.EmailTemplate.EmailVerifyFormTitle, EmailVerifyFormContent = src.EmailTemplate.EmailVerifyFormContent, ChangedEmailFormTitle = src.EmailTemplate.ChangedEmailFormTitle, ChangedEmailFormContent = src.EmailTemplate.ChangedEmailFormContent }, External = new ExternalApiConfigDto { YouTubeApiKeyEnc = src.External.YouTubeApiKeyEnc, YouTubeApiName = src.External.YouTubeApiName, GoogleClientId = src.External.GoogleClientId, GoogleClientSecretEnc = src.External.GoogleClientSecretEnc, GoogleAppId = src.External.GoogleAppId }, // 토스 키는 화면에 되돌리지 않는다 (암호문 재제출로 인한 이중 암호화 방지 — 빈 값 = 기존 유지) Toss = new TossConfigDto { TossPayMode = src.Toss.TossPayMode }, Attendance = new AttendanceConfigDto { IsEnabled = src.Attendance.IsEnabled, BaseExp = src.Attendance.BaseExp, BasePoint = src.Attendance.BasePoint, UseStreakBonus = src.Attendance.UseStreakBonus, StreakBonusPerDay = src.Attendance.StreakBonusPerDay, StreakBonusPointPerDay = src.Attendance.StreakBonusPointPerDay, StreakBonusMaxDays = src.Attendance.StreakBonusMaxDays, UseRankBonus = src.Attendance.UseRankBonus, RankBonusConfig = src.Attendance.RankBonusConfig }, SignupReward = new SignupRewardConfigDto { Enabled = src.SignupReward.Enabled, CoinAmount = src.SignupReward.CoinAmount, CashAmount = src.SignupReward.CashAmount, ExpAmount = src.SignupReward.ExpAmount } }; }