2using Dreamine.Identity;
5using Microsoft.AspNetCore.Components.Forms;
140 public IReadOnlyList<PostEntry>
Posts {
get;
private set; } = [];
149 public IReadOnlyList<AlbumInfo>
Albums {
get;
private set; } = [];
314 public async Task<bool>
LoginAsync(
string slug, CancellationToken ct =
default)
316 var config = await
_tenants.GetAsync(slug, ct).ConfigureAwait(
false);
317 if (config is
null) {
StatusMessage =
"존재하지 않는 슬러그입니다.";
return false; }
319 var user = await
_userContext.GetCurrentAsync().ConfigureAwait(
false);
331 var verification = DreaminePasswordHasher.VerifyPassword(
LoginPassword, config.PasswordHash, out var upgradedHash);
332 IsAuthenticated = verification is not PasswordHashVerificationResult.Failed;
339 if (verification is PasswordHashVerificationResult.SuccessRehashNeeded && upgradedHash is not
null)
341 config.PasswordHash = upgradedHash;
342 await
_tenants.SaveAsync(config, ct).ConfigureAwait(
false);
345 if (user.IsAuthenticated &&
string.IsNullOrWhiteSpace(config.OwnerUserId))
347 config.OwnerUserId = user.Id;
348 config.OwnerProvider = user.Provider;
349 config.OwnerEmail = user.Email;
350 config.OwnerDisplayName = user.DisplayName;
351 config.OwnerLinkedAt = DateTime.Now;
353 await
_tenants.SaveAsync(config, ct).ConfigureAwait(
false);
356 StatusMessage =
"CodeMaru/Dreamine 계정에 연결되었습니다. 다음부터는 공용 로그인으로 관리할 수 있습니다.";
358 else if (user.IsAuthenticated)
361 await
_tenants.SaveAsync(config, ct).ConfigureAwait(
false);
364 StatusMessage =
"현재 CodeMaru/Dreamine 계정이 이 가족 앨범의 관리자로 추가되었습니다.";
366 else if (!user.IsAuthenticated &&
string.IsNullOrWhiteSpace(config.OwnerUserId))
368 StatusMessage =
"로그인은 성공했습니다. 공용 계정에 연결하려면 먼저 CodeMaru/Dreamine 로그인을 해주세요.";
415 var config = await
_tenants.GetAsync(slug, ct).ConfigureAwait(
false);
421 var user = await
_userContext.GetCurrentAsync().ConfigureAwait(
false);
428 await
LoadAsync(slug, ct).ConfigureAwait(
false);
464 public async Task
LoadAsync(
string slug, CancellationToken ct =
default)
468 Posts = await
_posts.GetAllAsync(slug, ct).ConfigureAwait(
false);
469 Albums = await
_albums.GetAllAsync(slug, ct).ConfigureAwait(
false);
507 if (
Config is
null ||
string.IsNullOrWhiteSpace(userId))
512 var user = await
_userContext.GetCurrentAsync().ConfigureAwait(
false);
519 if (
string.Equals(
Config.OwnerUserId, userId, StringComparison.Ordinal))
525 var removed =
Config.AdminUsers.RemoveAll(x =>
526 string.Equals(x.UserId, userId, StringComparison.Ordinal)) > 0;
555 var user = await
_userContext.GetCurrentAsync().ConfigureAwait(
false);
558 ?
string.IsNullOrWhiteSpace(user.DisplayName)
560 : $
"{user.DisplayName} ({user.Provider})"
597 user.IsAuthenticated &&
598 string.Equals(config.
OwnerUserId, user.Id, StringComparison.Ordinal);
633 user.IsAuthenticated &&
635 config.
AdminUsers.Any(x =>
string.Equals(x.UserId, user.Id, StringComparison.Ordinal)));
671 var existing = config.
AdminUsers.FirstOrDefault(x =>
672 string.Equals(x.UserId, user.Id, StringComparison.Ordinal));
673 if (existing is
null)
678 Provider = user.Provider,
680 DisplayName = user.DisplayName,
682 AddedAt = DateTime.Now
687 existing.Provider = user.Provider;
688 existing.Email = user.Email;
689 existing.DisplayName = user.DisplayName;
690 if (
string.Equals(role,
"Owner", StringComparison.OrdinalIgnoreCase))
692 existing.Role =
"Owner";
722 var result =
new List<FamilyAdminUser>();
723 if (!
string.IsNullOrWhiteSpace(config.
OwnerUserId))
732 AddedAt = config.OwnerLinkedAt ?? DateTime.MinValue
738 var existing = result.FirstOrDefault(x =>
739 string.Equals(x.UserId, admin.UserId, StringComparison.Ordinal));
740 if (existing is
null)
744 else if (
string.Equals(existing.Role,
"Owner", StringComparison.OrdinalIgnoreCase))
746 existing.Provider =
string.IsNullOrWhiteSpace(existing.Provider) ? admin.Provider : existing.Provider;
747 existing.Email =
string.IsNullOrWhiteSpace(existing.Email) ? admin.Email : existing.Email;
748 existing.DisplayName =
string.IsNullOrWhiteSpace(existing.DisplayName) ? admin.DisplayName : existing.DisplayName;
753 .OrderBy(x =>
string.Equals(x.Role,
"Owner", StringComparison.OrdinalIgnoreCase) ? 0 : 1)
754 .ThenBy(x => x.AddedAt)
784 if (
Config is
null)
return;
787 Config.PasswordHash = DreaminePasswordHasher.HashPlainTextForStorage(
Config.PasswordHash);
791 catch (Exception ex) {
StatusMessage = $
"저장 오류: {ex.Message}"; }
887 var fn = await
_media.UploadPostMediaAsync(slug,
EditingPost.Id, file, ct).ConfigureAwait(
false);
888 var ext = Path.GetExtension(fn).ToLowerInvariant();
889 if (ext is
".mp4" or
".webm" or
".mov" or
".m4v")
897 catch (Exception ex) {
StatusMessage = $
"업로드 오류: {ex.Message}"; }
933 public async Task
SavePostAsync(
string slug, CancellationToken ct =
default)
943 Posts = await
_posts.GetAllAsync(slug, ct).ConfigureAwait(
false);
947 catch (Exception ex) {
StatusMessage = $
"저장 오류: {ex.Message}"; }
991 public async Task
DeletePostAsync(
string slug,
string postId, CancellationToken ct =
default)
995 await
_posts.DeleteAsync(slug, postId, ct).ConfigureAwait(
false);
996 Posts = await
_posts.GetAllAsync(slug, ct).ConfigureAwait(
false);
999 catch (Exception ex) {
StatusMessage = $
"삭제 오류: {ex.Message}"; }
1050 public async Task
DeletePostMediaAsync(
string slug,
string postId,
string fileName, CancellationToken ct =
default)
1054 await
_media.DeletePostMediaAsync(slug, postId, fileName, ct).ConfigureAwait(
false);
1062 catch (Exception ex) {
StatusMessage = $
"삭제 오류: {ex.Message}"; }
1105 public async Task
UploadCoverAsync(
string slug, IBrowserFile file, CancellationToken ct =
default)
1110 await
_media.UploadCoverAsync(slug, file, ct).ConfigureAwait(
false);
1111 Config = await
_tenants.GetAsync(slug, ct).ConfigureAwait(
false);
1114 catch (Exception ex) {
StatusMessage = $
"업로드 오류: {ex.Message}"; }
1156 await
_albums.SaveAsync(slug, album, ct).ConfigureAwait(
false);
1157 Albums = await
_albums.GetAllAsync(slug, ct).ConfigureAwait(
false);
1162 catch (Exception ex) {
StatusMessage = $
"앨범 추가 오류: {ex.Message}"; }
1205 public async Task
DeleteAlbumAsync(
string slug,
string albumId, CancellationToken ct =
default)
1209 await
_albums.DeleteAsync(slug, albumId, ct).ConfigureAwait(
false);
1210 Albums = await
_albums.GetAllAsync(slug, ct).ConfigureAwait(
false);
1213 catch (Exception ex) {
StatusMessage = $
"앨범 삭제 오류: {ex.Message}"; }
1250 await
_tenants.DeleteAsync(slug, ct).ConfigureAwait(
false);
1326 public string GetMediaUrl(
string slug,
string postId,
string fileName) =>
_media.GetMediaUrl(slug, postId, fileName);
1367 public string GetThumbUrl(
string slug,
string postId,
string fileName) =>
_media.GetThumbUrl(slug, postId, fileName);
1394 url.Contains(
"youtube.com", StringComparison.OrdinalIgnoreCase) ||
1395 url.Contains(
"youtu.be/", StringComparison.OrdinalIgnoreCase);
record FamilyCurrentUser(string Id, string Provider, string Email, string DisplayName)
List< FamilyAdminUser > AdminUsers
readonly IFamilyTenantStore _tenants
async Task RefreshCurrentUserAsync()
async Task UploadCoverAsync(string slug, IBrowserFile file, CancellationToken ct=default)
string GetCoverUrl(string slug, string fileName)
FamilyAdminViewModel(IFamilyTenantStore tenants, IPostStore posts, IAlbumStore albums, IMediaService media, FamilyUserContext userContext)
readonly IPostStore _posts
async Task DeleteSelfAsync(string slug, CancellationToken ct=default)
string GetMediaUrl(string slug, string postId, string fileName)
async Task LoadAsync(string slug, CancellationToken ct=default)
readonly IMediaService _media
readonly IAlbumStore _albums
static bool IsAdminUser(FamilyConfig config, FamilyCurrentUser user)
IReadOnlyList< AlbumInfo > Albums
static bool IsOwnerUser(FamilyConfig config, FamilyCurrentUser user)
IReadOnlyList< FamilyAdminUser > EffectiveAdminUsers
IReadOnlyList< PostEntry > Posts
async Task DeletePostMediaAsync(string slug, string postId, string fileName, CancellationToken ct=default)
static bool IsYouTube(string url)
static IReadOnlyList< FamilyAdminUser > BuildEffectiveAdminUsers(FamilyConfig config)
async Task UploadPendingFilesAsync(string slug, CancellationToken ct=default)
async Task DeleteAlbumAsync(string slug, string albumId, CancellationToken ct=default)
async Task SavePostAsync(string slug, CancellationToken ct=default)
static void EnsureAdminUser(FamilyConfig config, FamilyCurrentUser user, string role)
void EditPost(PostEntry post)
List< IBrowserFile > PendingFiles
async Task DeletePostAsync(string slug, string postId, CancellationToken ct=default)
readonly FamilyUserContext _userContext
async Task InitializeAsync(string slug, CancellationToken ct=default)
async Task RemoveAdminAsync(string userId, CancellationToken ct=default)
async Task AddAlbumAsync(string slug, CancellationToken ct=default)
async Task< bool > LoginAsync(string slug, CancellationToken ct=default)
string GetThumbUrl(string slug, string postId, string fileName)
async Task SaveConfigAsync(CancellationToken ct=default)
bool IsLinkedToCurrentUser