Codemaru 1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
로딩중...
검색중...
일치하는것 없음
CardHybridCircuitSession.cs
이 파일의 문서화 페이지로 가기
1using System.Diagnostics;
2using System.Security.Claims;
5using Dreamine.Identity;
6using Microsoft.AspNetCore.Components.Authorization;
7
9
26public sealed class CardHybridCircuitSession : IDisposable
27{
36 private readonly SemaphoreSlim _saveLock = new(1, 1);
45 private readonly List<CardHistoryEntry> _history = new();
54 private readonly IQrSvgGenerator _qr;
63 private readonly ICardProfileStore _store;
72 private readonly AuthenticationStateProvider? _authProvider;
73
82 private CardHybridState _state;
100 private bool _hasGuestChanges;
109 private bool _initialized;
110
153 ICardProfileStore store,
154 AuthenticationStateProvider? authProvider = null)
155 {
156 _qr = qr ?? throw new ArgumentNullException(nameof(qr));
157 _store = store ?? throw new ArgumentNullException(nameof(store));
158 _authProvider = authProvider;
159 _state = CardHybridState.CreateDefault(_qr.CreateSvg(CardProfile.Default.LandingUrl));
160 }
161
170 public CardHybridState State => _state;
180
190
199 public event EventHandler<CardHybridState>? StateChanged;
200
233 public async Task EnsureInitializedAsync(CancellationToken cancellationToken = default)
234 {
235 if (_initialized)
236 {
237 return;
238 }
239 _initialized = true;
240
241 if (_authProvider is null)
242 {
243 return;
244 }
245
246 var authState = await _authProvider.GetAuthenticationStateAsync().ConfigureAwait(false);
247 var user = MapPrincipalToUser(authState.User);
248 await ApplyUserAsync(user, cancellationToken).ConfigureAwait(false);
249 }
250
275 private static CardHybridUser MapPrincipalToUser(ClaimsPrincipal principal)
276 {
277 if (principal?.Identity?.IsAuthenticated != true)
278 {
279 return CardHybridUser.Guest;
280 }
281
282 var userIdClaim = principal.FindFirstValue(DreamineIdentityExtensions.UserIdClaimType);
283 if (string.IsNullOrEmpty(userIdClaim))
284 {
285 return CardHybridUser.Guest;
286 }
287
288 var email = principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty;
289 var name = principal.FindFirstValue(ClaimTypes.Name)
290 ?? principal.FindFirstValue(ClaimTypes.GivenName)
291 ?? string.Empty;
292
293 return new CardHybridUser(
294 Id: $"oauth-{userIdClaim}",
295 Email: email,
296 DisplayName: string.IsNullOrWhiteSpace(name) ? email : name,
297 SignedInAt: DateTime.UtcNow);
298 }
299
332 public async Task ApplyUserAsync(CardHybridUser user, CancellationToken cancellationToken = default)
333 {
334 ArgumentNullException.ThrowIfNull(user);
335
336 _currentUser = user;
337 var snapshot = await _store.LoadAsync(user.Id, cancellationToken).ConfigureAwait(false);
338 LoadSnapshot(snapshot);
339 _hasGuestChanges = false;
341 }
342
391 public async Task ReplaceUserSnapshotAsync(CardHybridSnapshot snapshot, CancellationToken cancellationToken = default)
392 {
393 ArgumentNullException.ThrowIfNull(snapshot);
394
395 if (_currentUser.IsGuest)
396 {
397 throw new InvalidOperationException("Cannot replace snapshot for guest user.");
398 }
399
400 var withUserId = snapshot with { UserId = _currentUser.Id };
401 LoadSnapshot(withUserId);
402 _hasGuestChanges = false;
403
404 await _store.SaveAsync(_currentUser.Id, withUserId, cancellationToken).ConfigureAwait(false);
406 }
407
425 new(_currentUser.Id, _state.Profile, _history.ToArray(), DateTime.Now);
426
451 public void ImportSnapshot(CardHybridSnapshot snapshot)
452 {
453 ArgumentNullException.ThrowIfNull(snapshot);
454 LoadSnapshot(snapshot);
455 if (_currentUser.IsGuest)
456 {
457 _hasGuestChanges = true;
458 }
460 }
461
478 public void UpdateProfile(CardProfile profile)
479 {
480 var payload = profile.LandingUrl;
481 _state = _state with
482 {
483 Profile = profile,
484 QrPayload = payload,
485 QrSvg = _qr.CreateSvg(payload),
486 LastUpdated = DateTime.Now
487 };
488 MarkDirty();
491 }
492
510 {
511 var entry = new CardHistoryEntry(
512 Id: Guid.NewGuid(),
513 Profile: _state.Profile,
514 LandingUrl: _state.Profile.LandingUrl,
515 QrPayload: _state.QrPayload,
516 CreatedAt: DateTime.Now);
517
518 _history.Insert(0, entry);
519 _state = _state with { History = _history.ToArray(), LastUpdated = DateTime.Now };
520 MarkDirty();
523 return entry;
524 }
525
559 {
560 var index = _history.FindIndex(e => e.Id == id);
561 if (index < 0)
562 return null;
563
564 var payload = profile.LandingUrl;
565 var entry = _history[index] with
566 {
567 Profile = profile,
568 LandingUrl = payload,
569 QrPayload = payload,
570 CreatedAt = DateTime.Now
571 };
572 _history[index] = entry;
573 _state = _state with
574 {
575 Profile = profile,
576 QrPayload = payload,
577 QrSvg = _qr.CreateSvg(payload),
578 History = _history.ToArray(),
579 LastUpdated = DateTime.Now
580 };
581 MarkDirty();
584 return entry;
585 }
586
603 public void LoadHistory(CardHistoryEntry entry) => UpdateProfile(entry.Profile);
604
613 public void ClearHistory()
614 {
615 _history.Clear();
616 _state = _state with { History = Array.Empty<CardHistoryEntry>(), LastUpdated = DateTime.Now };
617 MarkDirty();
620 }
621
646 public bool RemoveHistory(Guid id)
647 {
648 var index = _history.FindIndex(e => e.Id == id);
649 if (index < 0)
650 return false;
651
652 _history.RemoveAt(index);
653 _state = _state with { History = _history.ToArray() };
654 MarkDirty();
657 return true;
658 }
659
676 private void LoadSnapshot(CardHybridSnapshot? snapshot)
677 {
678 _history.Clear();
679 if (snapshot?.History is not null)
680 _history.AddRange(snapshot.History);
681
682 var profile = snapshot?.Profile ?? CardProfile.Default;
683 _state = CardHybridState.Create(profile, _qr.CreateSvg(profile.LandingUrl), _history.ToArray());
684 }
685
694 private void MarkDirty()
695 {
696 if (_currentUser.IsGuest)
697 {
698 _hasGuestChanges = true;
699 }
700 }
701
710 private void PersistCurrent()
711 {
712 if (_currentUser.IsGuest)
713 {
714 return;
715 }
716
717 var snapshot = new CardHybridSnapshot(
718 _currentUser.Id, _state.Profile, _history.ToArray(), DateTime.Now);
719 var userId = _currentUser.Id;
720
721 _ = Task.Run(async () =>
722 {
723 await _saveLock.WaitAsync().ConfigureAwait(false);
724 try
725 {
726 await _store.SaveAsync(userId, snapshot).ConfigureAwait(false);
727 }
728 catch (Exception ex)
729 {
730 Debug.WriteLine($"CardHybridCircuitSession save failed: {ex}");
731 }
732 finally
733 {
734 _saveLock.Release();
735 }
736 });
737 }
738
747 private void NotifyStateChanged()
748 {
749 var handlers = StateChanged?.GetInvocationList();
750 if (handlers is null)
751 return;
752
753 foreach (var handler in handlers)
754 {
755 try { ((EventHandler<CardHybridState>)handler).Invoke(this, _state); }
756 catch (Exception ex) { Debug.WriteLine($"CardHybridCircuitSession handler failed: {ex}"); }
757 }
758 }
759
768 public void Dispose() => _saveLock.Dispose();
769}
record CardHybridSnapshot(string? UserId, CardProfile Profile, IReadOnlyList< CardHistoryEntry > History, DateTime SavedAt)
record CardHybridUser(string Id, string Email, string DisplayName, DateTime SignedInAt)
record CardProfile(string Brand, string Tagline, string BackBrand, string BackTagline, string Category, string Name, string Role, string Email, string Phone, string Address, string Website, string LandingSlug, string AccentColor, string ShortBio, string LandingDescription, string VCardNote, string InternalMemo, string? LogoImageDataUrl, bool RemoveLogoBackground, double CardWidthMm, double CardHeightMm, string FrontFontFamily, string BackFontFamily, double FrontBrandFontSize, double FrontTaglineFontSize, double FrontCategoryFontSize, double FrontEmailFontSize, double BackBrandFontSize, double BackTaglineFontSize, double BackNameFontSize, double BackRoleFontSize, string LandingTheme, bool IncludePhoneInVCard, bool IncludeAddressInVCard, string? ImportedFrontImageDataUrl, string? ImportedBackImageDataUrl)
record CardHistoryEntry(Guid Id, CardProfile Profile, string LandingUrl, string QrPayload, DateTime CreatedAt)
void ImportSnapshot(CardHybridSnapshot snapshot)
CardHybridCircuitSession(IQrSvgGenerator qr, ICardProfileStore store, AuthenticationStateProvider? authProvider=null)
readonly? AuthenticationStateProvider _authProvider
static CardHybridUser MapPrincipalToUser(ClaimsPrincipal principal)
async Task ApplyUserAsync(CardHybridUser user, CancellationToken cancellationToken=default)
void LoadSnapshot(CardHybridSnapshot? snapshot)
async Task EnsureInitializedAsync(CancellationToken cancellationToken=default)
async Task ReplaceUserSnapshotAsync(CardHybridSnapshot snapshot, CancellationToken cancellationToken=default)
CardHistoryEntry? UpdateHistory(Guid id, CardProfile profile)