WeddingThankYou 1.0.0.0
결혼식 이후 감사 인사와 사진, 계좌 안내, 연락처를 모바일 페이지로 전달하는 감사장 서비스입니다.
로딩중...
검색중...
일치하는것 없음
Program.cs
이 파일의 문서화 페이지로 가기
1using System;
2using System.IO;
3using System.Net;
4using System.Net.Sockets;
5using Dreamine.Hybrid.Wpf.DependencyInjection;
6using Dreamine.Hybrid.Wpf.Hosting;
7using Dreamine.Identity;
8using Dreamine.Identity.Options;
9using Microsoft.AspNetCore.Components.Server;
10using Microsoft.Extensions.Configuration;
11using Microsoft.Extensions.DependencyInjection;
12using Microsoft.Extensions.Hosting;
13using WeddingThankYou.Blazor;
15
16namespace WeddingThankYou;
17
26public static class Program
27{
36 [STAThread]
37 public static void Main()
38 {
39 try
40 {
41 Run();
42 }
43 catch (Exception ex)
44 {
46 System.Windows.MessageBox.Show(
47 $"Wedding ThankYou 실행 중 오류가 발생했습니다.\n\n{ex.Message}\n\n자세한 내용은 로그를 확인하세요.",
48 "Wedding ThankYou",
49 System.Windows.MessageBoxButton.OK,
50 System.Windows.MessageBoxImage.Error);
51 }
52 }
53
62 private static void Run()
63 {
64 HostApplicationBuilder builder = Host.CreateApplicationBuilder();
65 builder.Configuration.AddUserSecrets("codemaru-oauth-2ba4e1b2");
66
67 int serverPort = GetInt(builder.Configuration, "WeddingServer:Port", 4088);
68 bool listenAnyIp = GetBool(builder.Configuration, "WeddingServer:ListenAnyIp", true);
69 bool useEmbeddedWebView = GetBool(builder.Configuration, "WeddingShell:UseEmbeddedWebView", true);
70 if (!IsTcpPortAvailable(serverPort, listenAnyIp))
71 {
72 serverPort = GetFreeLoopbackPort();
73 listenAnyIp = false;
74 }
75
76 AuthOptions authOptions =
77 builder.Configuration.GetSection(AuthOptions.SectionName).Get<AuthOptions>() ?? new AuthOptions();
78 string usersDbPath = ResolvePath(
79 builder.Configuration[$"{AuthOptions.SectionName}:UsersDbPath"],
80 Path.Combine(AppContext.BaseDirectory, "App_Data", "codemaru.db"));
81
82 builder.Services.AddDreamineHybridWpf();
83 builder.Services.AddDreamineIdentityWpfHost();
84
85 // ── Wedding 서비스 ─────────────────────────────────────────
86 var weddingOpts = WeddingOptions.From(builder.Configuration);
87 builder.Services.AddSingleton(weddingOpts);
88 builder.Services.AddSingleton<ITenantStore, JsonTenantStore>();
89 builder.Services.AddSingleton<IGuestbookStorage, CsvGuestbookStorage>();
90 builder.Services.AddSingleton<IGlobalSettingsStore, JsonGlobalSettingsStore>();
91 builder.Services.AddSingleton<IMediaQuotaPolicyResolver, MediaQuotaPolicyResolver>();
93 builder.Services.AddSingleton<IMediaMigrationService, JsonMediaMigrationService>();
94 builder.Services.AddSingleton<IMediaUsageQueryService, FileSystemMediaUsageQueryService>();
95 builder.Services.AddSingleton<ISuperAdminSessionTokenService, SuperAdminSessionTokenService>();
96 builder.Services.AddSingleton<IPhotoService, LocalPhotoService>();
97
98 builder.Services.AddSingleton<Views.MainWindow>();
99
100 builder.Services.AddDreamineBlazorServer<AppShell>(options =>
101 {
102 options.Port = serverPort;
103 options.ListenAnyIp = listenAnyIp;
104 options.UseEmbeddedWebView = useEmbeddedWebView;
105 // Wedding 서비스 Blazor 호스트에 공유 (ViewModel은 AutoRegisterViewModels로 자동 등록)
106 options.SharedServiceTypes.Add(typeof(WeddingOptions));
107 options.SharedServiceTypes.Add(typeof(ITenantStore));
108 options.SharedServiceTypes.Add(typeof(IGuestbookStorage));
109 options.SharedServiceTypes.Add(typeof(IGlobalSettingsStore));
110 options.SharedServiceTypes.Add(typeof(IMediaQuotaPolicyResolver));
111 options.SharedServiceTypes.Add(typeof(IImageOptimizationService));
112 options.SharedServiceTypes.Add(typeof(IMediaMigrationService));
113 options.SharedServiceTypes.Add(typeof(IMediaUsageQueryService));
114 options.SharedServiceTypes.Add(typeof(ISuperAdminSessionTokenService));
115 options.SharedServiceTypes.Add(typeof(IPhotoService));
116 // 업로드된 사진/미디어를 /wedding-data/ URL로 제공
117 options.AddPhysicalStaticFiles(weddingOpts.ResolvedDataPath, "/wedding-data");
118
119 // InputFile(동영상 등 대용량 업로드)은 SignalR 회선을 통해 청크 단위로 전송되는데,
120 // 기본 SignalR 메시지 크기 제한(32KB)과 짧은 타임아웃 때문에 큰 파일은 매우 느리거나
121 // 응답 없이 멈춘 것처럼 보입니다. 업로드 용량 검증은 LocalPhotoService에서 별도로
122 // 하고 있으므로 전송 한도 자체는 풀어줍니다.
123 options.ConfigureServices = services =>
124 {
125 services.AddServerSideBlazor()
126 .AddHubOptions(o =>
127 {
128 o.MaximumReceiveMessageSize = null; // 무제한 (용량 검증은 LocalPhotoService에서 수행)
129 o.ClientTimeoutInterval = TimeSpan.FromMinutes(10);
130 o.HandshakeTimeout = TimeSpan.FromMinutes(2);
131 o.KeepAliveInterval = TimeSpan.FromSeconds(10);
132 });
133
134 services.AddAntiforgery(o =>
135 {
136 o.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
137 o.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest;
138 });
139
140 services.Configure<CircuitOptions>(o =>
141 {
142 o.DisconnectedCircuitMaxRetained = 100;
143 o.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
144 o.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
145 o.MaxBufferedUnacknowledgedRenderBatches = 10;
146 });
147
148 services.AddScoped<ThankYouUserContext>();
149 };
150
151 options.AddDreamineIdentity(authOptions, usersDbPath);
152 });
153
154 builder.Build().RunDreamineWpfApp<App>();
155 }
156
189 private static bool IsTcpPortAvailable(int port, bool listenAnyIp)
190 {
191 if (port <= 0) return false;
192
193 TcpListener? listener = null;
194 try
195 {
196 listener = new TcpListener(listenAnyIp ? IPAddress.Any : IPAddress.Loopback, port);
197 listener.Start();
198 return true;
199 }
200 catch (SocketException)
201 {
202 return false;
203 }
204 finally
205 {
206 listener?.Stop();
207 }
208 }
209
226 private static int GetFreeLoopbackPort()
227 {
228 var listener = new TcpListener(IPAddress.Loopback, 0);
229 listener.Start();
230 try
231 {
232 return ((IPEndPoint)listener.LocalEndpoint).Port;
233 }
234 finally
235 {
236 listener.Stop();
237 }
238 }
239
256 private static void WriteStartupFailureLog(Exception ex)
257 {
258 try
259 {
260 var dir = Path.Combine(
261 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
262 "WeddingThankYou",
263 "Logs");
264 Directory.CreateDirectory(dir);
265 var path = Path.Combine(dir, "startup-error.log");
266 File.AppendAllText(path, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {ex}\r\n\r\n");
267 }
268 catch
269 {
270 // 마지막 안전망: 오류 로그 작성 실패가 앱 종료 원인이 되지 않게 둡니다.
271 }
272 }
273
314 private static int GetInt(IConfiguration cfg, string key, int fallback) =>
315 int.TryParse(cfg[key], out int v) ? v : fallback;
316
357 private static bool GetBool(IConfiguration cfg, string key, bool fallback) =>
358 bool.TryParse(cfg[key], out bool v) ? v : fallback;
359
392 private static string ResolvePath(string? configuredPath, string fallback)
393 {
394 if (string.IsNullOrWhiteSpace(configuredPath))
395 {
396 return fallback;
397 }
398
399 return Path.IsPathRooted(configuredPath)
400 ? configuredPath
401 : Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, configuredPath));
402 }
403}
static int GetFreeLoopbackPort()
Definition Program.cs:226
static void Main()
Definition Program.cs:37
static void Run()
Definition Program.cs:62
static string ResolvePath(string? configuredPath, string fallback)
Definition Program.cs:392
static bool GetBool(IConfiguration cfg, string key, bool fallback)
Definition Program.cs:357
static void WriteStartupFailureLog(Exception ex)
Definition Program.cs:256
static bool IsTcpPortAvailable(int port, bool listenAnyIp)
Definition Program.cs:189
static int GetInt(IConfiguration cfg, string key, int fallback)
Definition Program.cs:314
static WeddingOptions From(IConfiguration configuration)