diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 06552962..da73e5e3 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -20,6 +20,7 @@
+
@@ -50,7 +51,6 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
all
runtime; build; native; contentfiles; analyzers
@@ -59,7 +59,6 @@
all
runtime; build; native; contentfiles; analyzers
-
@@ -103,6 +102,7 @@
+
diff --git a/src/MZikmund.sln b/src/MZikmund.sln
index c6855a74..e84e2aa5 100644
--- a/src/MZikmund.sln
+++ b/src/MZikmund.sln
@@ -42,8 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MZikmund.Skia.Gtk", "app\MZ
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MZikmund.Skia.Linux.FrameBuffer", "app\MZikmund.Skia.Linux.FrameBuffer\MZikmund.Skia.Linux.FrameBuffer.csproj", "{0EBB04BE-4BB8-4F44-AA52-04B1967FCCEC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MZikmund.Skia.WPF", "app\MZikmund.Skia.WPF\MZikmund.Skia.WPF.csproj", "{CAD5D4B9-FB38-4997-A9A8-10B89282518E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MZikmund.Wasm", "app\MZikmund.Wasm\MZikmund.Wasm.csproj", "{DBCC4BA1-A25D-4ECA-B599-CE5B14E74B67}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MZikmund.Windows", "app\MZikmund.Windows\MZikmund.Windows.csproj", "{6A207857-8DAD-47D3-AAEB-CEF69C468841}"
@@ -760,62 +758,6 @@ Global
{0EBB04BE-4BB8-4F44-AA52-04B1967FCCEC}.Release|x64.Build.0 = Release|Any CPU
{0EBB04BE-4BB8-4F44-AA52-04B1967FCCEC}.Release|x86.ActiveCfg = Release|Any CPU
{0EBB04BE-4BB8-4F44-AA52-04B1967FCCEC}.Release|x86.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|ARM64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|ARM64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|ARM.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|ARM.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|ARM64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|ARM64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|iPhone.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|x64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|x64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|x86.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.AppStore|x86.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|ARM.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|ARM64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|iPhone.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|x64.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Debug|x86.Build.0 = Debug|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|Any CPU.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|ARM.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|ARM.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|ARM64.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|ARM64.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|iPhone.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|iPhone.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|x64.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|x64.Build.0 = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|x86.ActiveCfg = Release|Any CPU
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E}.Release|x86.Build.0 = Release|Any CPU
{DBCC4BA1-A25D-4ECA-B599-CE5B14E74B67}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{DBCC4BA1-A25D-4ECA-B599-CE5B14E74B67}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{DBCC4BA1-A25D-4ECA-B599-CE5B14E74B67}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
@@ -1114,7 +1056,6 @@ Global
{0D99E167-C1F3-4996-875B-9E9EB969B722} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
{BA5F6CFD-EEF6-467A-A99B-E5001DB1259F} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
{0EBB04BE-4BB8-4F44-AA52-04B1967FCCEC} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
- {CAD5D4B9-FB38-4997-A9A8-10B89282518E} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
{DBCC4BA1-A25D-4ECA-B599-CE5B14E74B67} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
{6A207857-8DAD-47D3-AAEB-CEF69C468841} = {143CCBE2-8344-4BC0-9812-FA6B3EA703EA}
{C2E30D34-5B71-432F-B5D5-69B88B5AF5DB} = {6AD11812-0C83-402E-83D6-6D46B2CB7AF5}
diff --git a/src/app/MZikmund.Windows/MZikmund.Windows.csproj b/src/app/MZikmund.Windows/MZikmund.Windows.csproj
index 260aa299..02d82be9 100644
--- a/src/app/MZikmund.Windows/MZikmund.Windows.csproj
+++ b/src/app/MZikmund.Windows/MZikmund.Windows.csproj
@@ -1,4 +1,4 @@
-
+
WinExe
net8.0-windows10.0.19041.0
@@ -11,7 +11,7 @@
true
- true
+
diff --git a/src/app/MZikmund/App.cs b/src/app/MZikmund/App.cs
index 94d98ffb..a84a6cfc 100644
--- a/src/app/MZikmund/App.cs
+++ b/src/app/MZikmund/App.cs
@@ -131,6 +131,7 @@ private static void ConfigureServices(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddSingleton();
services.AddSingleton(provider =>
{
var configuration = provider.GetRequiredService>();
@@ -144,7 +145,7 @@ async Task GetTokenAsync(HttpRequestMessage message, CancellationToken c
{
//TODO: Move somewhere more appropriate and integrate refresh token support
var userService = Ioc.Default.GetRequiredService();
- if (!userService.IsLoggedIn)
+ if (!userService.IsLoggedIn || userService.NeedsRefresh)
{
await userService.AuthenticateAsync();
}
diff --git a/src/app/MZikmund/MZikmund.csproj b/src/app/MZikmund/MZikmund.csproj
index 05253616..950f97cc 100644
--- a/src/app/MZikmund/MZikmund.csproj
+++ b/src/app/MZikmund/MZikmund.csproj
@@ -10,10 +10,12 @@
+
all
+
@@ -27,6 +29,7 @@
+
diff --git a/src/app/MZikmund/Services/Account/AuthenticationConstants.cs b/src/app/MZikmund/Services/Account/AuthenticationConstants.cs
new file mode 100644
index 00000000..61ad64a6
--- /dev/null
+++ b/src/app/MZikmund/Services/Account/AuthenticationConstants.cs
@@ -0,0 +1,16 @@
+namespace MZikmund.Services.Account;
+
+public static class AuthenticationConstants
+{
+ public const string ApplicationId = "7e13557a-4799-46b8-9e2b-0f31c41a051e";
+
+ public const string TenantId = "4e973842-1a98-40ec-9542-3c2019f0fb8e";
+
+ public static string[] DefaultScopes { get; } = new string[]
+ {
+ "api://862d5839-f30f-41a9-ab6f-ff7eef19342c/access_as_user",
+ "user.read",
+ "profile",
+ "offline_access"
+ };
+}
diff --git a/src/app/MZikmund/Services/Account/AuthenticationInfo.cs b/src/app/MZikmund/Services/Account/AuthenticationInfo.cs
new file mode 100644
index 00000000..d566d278
--- /dev/null
+++ b/src/app/MZikmund/Services/Account/AuthenticationInfo.cs
@@ -0,0 +1,12 @@
+namespace MZikmund.Services.Account;
+
+public class AuthenticationInfo
+{
+ public required string DisplayName { get; init; }
+
+ public required DateTimeOffset ExpiresOn { get; init; }
+
+ public required string Token { get; init; }
+
+ public required string UserId { get; init; }
+}
diff --git a/src/app/MZikmund/Services/Account/IUserService.cs b/src/app/MZikmund/Services/Account/IUserService.cs
index 2282fc3a..17d47ae0 100644
--- a/src/app/MZikmund/Services/Account/IUserService.cs
+++ b/src/app/MZikmund/Services/Account/IUserService.cs
@@ -4,6 +4,10 @@ public interface IUserService
{
bool IsLoggedIn { get; }
+ bool NeedsRefresh { get; }
+
+ string? UserName { get; }
+
string? AccessToken { get; }
Task AuthenticateAsync();
diff --git a/src/app/MZikmund/Services/Account/UserService.cs b/src/app/MZikmund/Services/Account/UserService.cs
new file mode 100644
index 00000000..30aa2fa1
--- /dev/null
+++ b/src/app/MZikmund/Services/Account/UserService.cs
@@ -0,0 +1,129 @@
+using Microsoft.Identity.Client;
+using Uno.UI.MSAL;
+using MZikmund.Services.Preferences;
+using Microsoft.Identity.Client.Extensions.Msal;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace MZikmund.Services.Account;
+
+public class UserService : IUserService
+{
+ private IPublicClientApplication? _identityClient;
+ private AuthenticationInfo? _authenticationInfo;
+
+ public UserService()
+ {
+ }
+
+ public bool IsLoggedIn => _authenticationInfo != null;
+
+ public string? UserName => _authenticationInfo?.DisplayName;
+
+ public bool NeedsRefresh => _authenticationInfo?.ExpiresOn < DateTimeOffset.UtcNow.AddMinutes(-5);
+
+ public string? AccessToken => _authenticationInfo?.Token;
+
+ public async Task AuthenticateAsync()
+ {
+ if (IsLoggedIn && !NeedsRefresh)
+ {
+ return;
+ }
+
+ await EnsureIdentityClientAsync();
+
+ var accounts = await _identityClient.GetAccountsAsync();
+ AuthenticationResult? result = null;
+ bool tryInteractiveLogin = false;
+
+ try
+ {
+ result = await _identityClient
+ .AcquireTokenSilent(AuthenticationConstants.DefaultScopes, accounts.FirstOrDefault())
+ .ExecuteAsync();
+ }
+ catch (MsalUiRequiredException)
+ {
+ tryInteractiveLogin = true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"MSAL Silent Error: {ex.Message}");
+ }
+
+ if (tryInteractiveLogin)
+ {
+ try
+ {
+ result = await _identityClient
+ .AcquireTokenInteractive(AuthenticationConstants.DefaultScopes)
+ .ExecuteAsync();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"MSAL Interactive Error: {ex.Message}");
+ }
+ }
+
+ _authenticationInfo = new AuthenticationInfo
+ {
+ DisplayName = result?.Account?.Username ?? "",
+ ExpiresOn = result?.ExpiresOn ?? DateTimeOffset.MinValue,
+ Token = result?.AccessToken ?? "",
+ UserId = result?.Account?.Username ?? ""
+ };
+ }
+
+ [MemberNotNull(nameof(_identityClient))]
+ private async Task EnsureIdentityClientAsync()
+ {
+ if (_identityClient == null)
+ {
+#if __ANDROID__
+ _identityClient = PublicClientApplicationBuilder
+ .Create(AuthenticationConstants.ApplicationId)
+ .WithAuthority(AzureCloudInstance.AzurePublic, AuthenticationConstants.TenantId)
+ .WithRedirectUri($"msal{AuthenticationConstants.ApplicationId}://auth")
+ .WithParentActivityOrWindow(() => ContextHelper.Current)
+ .Build();
+
+ await Task.CompletedTask;
+#elif __IOS__
+ _identityClient = PublicClientApplicationBuilder
+ .Create(AuthenticationConstants.ApplicationId)
+ .WithAuthority(AzureCloudInstance.AzurePublic, AuthenticationConstants.TenantId)
+ .WithIosKeychainSecurityGroup("com.microsoft.adalcache")
+ .WithRedirectUri($"msal{AuthenticationConstants.ApplicationId}://auth")
+ .Build();
+
+ await Task.CompletedTask;
+#else
+ _identityClient = PublicClientApplicationBuilder
+ .Create(AuthenticationConstants.ApplicationId)
+ .WithAuthority(AzureCloudInstance.AzurePublic, AuthenticationConstants.TenantId)
+ .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
+ .WithUnoHelpers()
+ .Build();
+
+ await AttachTokenCacheAsync();
+#endif
+ }
+ }
+
+#if !__ANDROID__ && !__IOS__
+ private async Task AttachTokenCacheAsync()
+ {
+#if !HAS_UNO
+ // Cache configuration and hook-up to public application. Refer to https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache#configuring-the-token-cache
+ var storageProperties = new StorageCreationPropertiesBuilder("msal.cache", ApplicationData.Current.LocalFolder.Path)
+ .Build();
+
+ var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
+ msalcachehelper.RegisterCache(_identityClient!.UserTokenCache);
+#else
+ await Task.CompletedTask;
+#endif
+ }
+#endif
+}
diff --git a/src/app/MZikmund/ViewModels/Admin/BlogCategoriesManagerViewModel.cs b/src/app/MZikmund/ViewModels/Admin/BlogCategoriesManagerViewModel.cs
index 5035137c..0e927957 100644
--- a/src/app/MZikmund/ViewModels/Admin/BlogCategoriesManagerViewModel.cs
+++ b/src/app/MZikmund/ViewModels/Admin/BlogCategoriesManagerViewModel.cs
@@ -32,12 +32,18 @@ public CategoriesManagerViewModel(
public ObservableCollection Categories { get; } = new ObservableCollection();
public override async void ViewAppeared()
+ {
+ await RefreshListAsync();
+ }
+
+ private async Task RefreshListAsync()
{
using var loadingScope = _loadingIndicator.BeginLoading();
try
{
//TODO: Refresh collection based on IDs
var categories = await _api.GetCategoriesAsync();
+ Categories.Clear();
Categories.AddRange(categories.Content!);
}
catch (Exception ex)
@@ -51,33 +57,7 @@ await _dialogService.ShowStatusMessageAsync(
public ICommand AddCategoryCommand => GetOrCreateAsyncCommand(AddCategoryAsync);
- public ICommand UpdateCategoryCommand => GetOrCreateAsyncCommand(UpdateCategoryAsync);
-
- public ICommand ImportJsonCommand => GetOrCreateAsyncCommand(ImportJsonAsync);
-
- private async Task ImportJsonAsync()
- {
- using var loadingScope = _loadingIndicator.BeginLoading();
- var picker = new FileOpenPicker();
- picker.FileTypeFilter.Add(".json");
- var jsonFile = await picker.PickSingleFileAsync();
- var jsonContent = await FileIO.ReadTextAsync(jsonFile);
- var Categories = JsonConvert.DeserializeObject(jsonContent);
- if (Categories == null)
- {
- return;
- }
-
- for (int i = 0; i < Categories.Length; i++)
- {
- var tag = Categories[i];
- _loadingIndicator.StatusMessage = $"Adding tag {i + 1} of {Categories.Length}";
- // Ensure tag ID is empty.
- tag.Id = Guid.Empty;
-
- await _api.AddCategoryAsync(tag);
- }
- }
+ public ICommand UpdateCategoryCommand => GetOrCreateAsyncCommand(UpdateCategoryAsync);
private async Task AddCategoryAsync()
{
@@ -88,34 +68,42 @@ private async Task AddCategoryAsync()
return;
}
- //var apiResponse = await _api.AddCategoryAsync(new CategoryDto()
- //{
- // Localizations = new[]
- // {
- // new CategoryLocalizationDto()
- // {
- // DisplayName = "test",
- // LanguageId = 1,
- // RouteName = "test"
- // }
- // }
- //});
+ var apiResponse = await _api.AddCategoryAsync(new Category()
+ {
+ DisplayName = viewModel.Category.DisplayName,
+ RouteName = viewModel.Category.RouteName,
+ });
+
+ await RefreshListAsync();
}
- private async Task UpdateCategoryAsync(CategoryViewModel? category)
+ private async Task UpdateCategoryAsync(Category? category)
{
if (category is null)
{
return;
}
- var viewModel = new AddOrUpdateCategoryDialogViewModel(category);
+ var viewModel = new AddOrUpdateCategoryDialogViewModel(new CategoryViewModel()
+ {
+ Id = category.Id,
+ DisplayName = category.DisplayName,
+ RouteName = category.RouteName
+ });
var result = await _dialogService.ShowAsync(viewModel);
- if (result != ContentDialogResult.Primary)
+ if (result == ContentDialogResult.Primary)
{
- return;
+ var apiResponse = await _api.UpdateCategoryAsync(category.Id, new EditCategory()
+ {
+ DisplayName = viewModel.Category.DisplayName,
+ RouteName = viewModel.Category.RouteName,
+ });
+ }
+ else if (result == ContentDialogResult.Secondary)
+ {
+ var apiResponse = await _api.DeleteCategoryAsync(category.Id);
}
- // TODO: Update category via API.
+ await RefreshListAsync();
}
}
diff --git a/src/app/MZikmund/ViewModels/Admin/BlogCategoryViewModel.cs b/src/app/MZikmund/ViewModels/Admin/BlogCategoryViewModel.cs
index 79bc4ac0..ae97de50 100644
--- a/src/app/MZikmund/ViewModels/Admin/BlogCategoryViewModel.cs
+++ b/src/app/MZikmund/ViewModels/Admin/BlogCategoryViewModel.cs
@@ -4,7 +4,7 @@ namespace MZikmund.ViewModels.Admin;
public class CategoryViewModel : ObservableObject
{
- public int Id { get; set; }
+ public Guid Id { get; set; }
public string DisplayName { get; set; } = "";
diff --git a/src/app/MZikmund/ViewModels/Admin/BlogTagViewModel.cs b/src/app/MZikmund/ViewModels/Admin/BlogTagViewModel.cs
index 11101d30..316e35a5 100644
--- a/src/app/MZikmund/ViewModels/Admin/BlogTagViewModel.cs
+++ b/src/app/MZikmund/ViewModels/Admin/BlogTagViewModel.cs
@@ -4,7 +4,7 @@ namespace MZikmund.ViewModels.Admin;
public class TagViewModel : ObservableObject
{
- public int Id { get; set; }
+ public Guid Id { get; set; }
public string DisplayName { get; set; } = "";
diff --git a/src/app/MZikmund/ViewModels/Admin/BlogTagsManagerViewModel.cs b/src/app/MZikmund/ViewModels/Admin/BlogTagsManagerViewModel.cs
index 5ab7bc12..33145bf3 100644
--- a/src/app/MZikmund/ViewModels/Admin/BlogTagsManagerViewModel.cs
+++ b/src/app/MZikmund/ViewModels/Admin/BlogTagsManagerViewModel.cs
@@ -1,13 +1,13 @@
using System.Collections.ObjectModel;
using MZikmund.Api.Client;
+using MZikmund.DataContracts.Blog;
using MZikmund.Extensions;
using MZikmund.Models.Dialogs;
using MZikmund.Services.Dialogs;
using MZikmund.Services.Loading;
+using MZikmund.Services.Localization;
using Newtonsoft.Json;
using Windows.Storage.Pickers;
-using MZikmund.Services.Localization;
-using MZikmund.DataContracts.Blog;
namespace MZikmund.ViewModels.Admin;
@@ -32,12 +32,18 @@ public TagsManagerViewModel(
public ObservableCollection Tags { get; } = new ObservableCollection();
public override async void ViewAppeared()
+ {
+ await RefreshListAsync();
+ }
+
+ private async Task RefreshListAsync()
{
using var loadingScope = _loadingIndicator.BeginLoading();
try
{
//TODO: Refresh collection based on IDs
var tags = await _api.GetTagsAsync();
+ Tags.Clear();
Tags.AddRange(tags.Content!);
}
catch (Exception ex)
@@ -51,79 +57,53 @@ await _dialogService.ShowStatusMessageAsync(
public ICommand AddTagCommand => GetOrCreateAsyncCommand(AddTagAsync);
- public ICommand ImportJsonCommand => GetOrCreateAsyncCommand(ImportJsonAsync);
+ public ICommand UpdateTagCommand => GetOrCreateAsyncCommand(UpdateTagAsync);
- private async Task ImportJsonAsync()
+ private async Task AddTagAsync()
{
- using var loadingScope = _loadingIndicator.BeginLoading();
- var picker = new FileOpenPicker();
- picker.FileTypeFilter.Add(".json");
- var jsonFile = await picker.PickSingleFileAsync();
- var jsonContent = await FileIO.ReadTextAsync(jsonFile);
- var tags = JsonConvert.DeserializeObject(jsonContent);
- if (tags == null)
+ var viewModel = new AddOrUpdateTagDialogViewModel();
+ var result = await _dialogService.ShowAsync(viewModel);
+ if (result != ContentDialogResult.Primary)
{
return;
}
- for (int i = 0; i < tags.Length; i++)
+ var apiResponse = await _api.AddTagAsync(new Tag()
{
- var tag = tags[i];
- _loadingIndicator.StatusMessage = $"Adding tag {i + 1} of {tags.Length}";
- // Ensure tag ID is empty.
- tag.Id = Guid.Empty;
+ DisplayName = viewModel.Tag.DisplayName,
+ RouteName = viewModel.Tag.RouteName,
+ });
- await _api.AddTagAsync(tag);
- }
+ await RefreshListAsync();
}
- private async Task AddTagAsync()
+ private async Task UpdateTagAsync(Tag? tag)
{
- var viewModel = new AddOrUpdateTagDialogViewModel();
- var result = await _dialogService.ShowAsync(viewModel);
- if (result != ContentDialogResult.Primary)
+ if (tag is null)
{
return;
}
- //var apiResponse = await _api.AddTagAsync(new TagDto()
- //{
- // Localizations = new[]
- // {
- // new TagLocalizationDto()
- // {
- // DisplayName = "test",
- // LanguageId = 1,
- // RouteName = "test"
- // }
- // }
- //});
- }
-
- public async Task UpdateTagAsync(TagViewModel tag)
- {
- var viewModel = new AddOrUpdateTagDialogViewModel(tag);
+ var viewModel = new AddOrUpdateTagDialogViewModel(new TagViewModel()
+ {
+ Id = tag.Id,
+ DisplayName = tag.DisplayName,
+ RouteName = tag.RouteName
+ });
var result = await _dialogService.ShowAsync(viewModel);
- if (result != ContentDialogResult.Primary)
+ if (result == ContentDialogResult.Primary)
{
- return;
+ var apiResponse = await _api.UpdateTagAsync(tag.Id, new EditTag()
+ {
+ DisplayName = viewModel.Tag.DisplayName,
+ RouteName = viewModel.Tag.RouteName,
+ });
+ }
+ else if (result == ContentDialogResult.Secondary)
+ {
+ var apiResponse = await _api.DeleteTagAsync(tag.Id);
}
- await Task.CompletedTask;
-
- // TODO: Update tag via API
-
- //var apiResponse = await _api.AddTagAsync(new TagDto()
- //{
- // Localizations = new[]
- // {
- // new TagLocalizationDto()
- // {
- // DisplayName = "test",
- // LanguageId = 1,
- // RouteName = "test"
- // }
- // }
- //});
+ await RefreshListAsync();
}
}
diff --git a/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml b/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml
index 3b676145..59ccdb10 100644
--- a/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml
+++ b/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml
@@ -2,49 +2,64 @@
x:Class="MZikmund.Views.Admin.CategoriesManagerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="using:MZikmund.Views.Admin"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:dto="using:MZikmund.DataContracts.Blog"
+ xmlns:local="using:MZikmund.Views.Admin"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xaml="using:MZikmund.Extensions.Xaml"
- xmlns:dto="using:MZikmund.DataContracts.Blog"
- mc:Ignorable="d"
- d:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+ d:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
+ mc:Ignorable="d">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml.cs b/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml.cs
index 0ff0d93d..bdbdea5e 100644
--- a/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml.cs
+++ b/src/app/MZikmund/Views/Admin/BlogCategoriesManagerView.xaml.cs
@@ -1,10 +1,17 @@
-using MZikmund.ViewModels.Admin;
+using MZikmund.DataContracts.Blog;
+using MZikmund.ViewModels.Admin;
namespace MZikmund.Views.Admin;
public sealed partial class CategoriesManagerView : CategoriesManagerViewBase
{
public CategoriesManagerView() => InitializeComponent();
+
+ private void GridView_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ var category = (Category)e.ClickedItem;
+ ViewModel!.UpdateCategoryCommand.Execute(category);
+ }
}
public partial class CategoriesManagerViewBase : PageBase
diff --git a/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml b/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml
index e10925de..7c01bcf1 100644
--- a/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml
+++ b/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml
@@ -2,49 +2,64 @@
x:Class="MZikmund.Views.Admin.TagsManagerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="using:MZikmund.Views.Admin"
- xmlns:dto="using:MZikmund.DataContracts.Blog"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:dto="using:MZikmund.DataContracts.Blog"
+ xmlns:local="using:MZikmund.Views.Admin"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xaml="using:MZikmund.Extensions.Xaml"
- mc:Ignorable="d"
- d:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+ d:Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
+ mc:Ignorable="d">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml.cs b/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml.cs
index 03a3d720..90494274 100644
--- a/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml.cs
+++ b/src/app/MZikmund/Views/Admin/BlogTagsManagerView.xaml.cs
@@ -1,10 +1,17 @@
-using MZikmund.ViewModels.Admin;
+using MZikmund.DataContracts.Blog;
+using MZikmund.ViewModels.Admin;
namespace MZikmund.Views.Admin;
public sealed partial class TagsManagerView : TagsManagerViewBase
{
public TagsManagerView() => InitializeComponent();
+
+ private void GridView_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ var tag = (Tag)e.ClickedItem;
+ ViewModel!.UpdateTagCommand.Execute(tag);
+ }
}
public partial class TagsManagerViewBase : PageBase
diff --git a/src/app/MZikmund/appsettings.json b/src/app/MZikmund/appsettings.json
index 1cc2e596..18073438 100644
--- a/src/app/MZikmund/appsettings.json
+++ b/src/app/MZikmund/appsettings.json
@@ -1,4 +1,8 @@
{
+ "Msal": {
+ "ClientId": "7e13557a-4799-46b8-9e2b-0f31c41a051e",
+ "Scopes": [ "User.Read" ]
+ },
"AppConfig": {
"Environment": "Production",
"ApiUrl": "https://mzikmund.dev/api"
diff --git a/src/shared/MZikmund.Api/IMZikmundApi.Tags.cs b/src/shared/MZikmund.Api/IMZikmundApi.Tags.cs
index 85ba5db4..d521a399 100644
--- a/src/shared/MZikmund.Api/IMZikmundApi.Tags.cs
+++ b/src/shared/MZikmund.Api/IMZikmundApi.Tags.cs
@@ -14,9 +14,9 @@ public partial interface IMZikmundApi
[Put("/v1/admin/tags/{tagId}")]
[Headers("Authorization: Bearer")]
- Task> UpdateTagAsync(int tagId, [Body] EditTag tag);
+ Task> UpdateTagAsync(Guid tagId, [Body] EditTag tag);
[Delete("/v1/admin/tags/{tagId}")]
[Headers("Authorization: Bearer")]
- Task> DeleteTagAsync(int tagId);
+ Task> DeleteTagAsync(Guid tagId);
}
diff --git a/src/web/MZikmund.Web.Core/Mappings/CategoryMap.cs b/src/web/MZikmund.Web.Core/Mappings/CategoryMap.cs
index 4ac35901..30a43d7f 100644
--- a/src/web/MZikmund.Web.Core/Mappings/CategoryMap.cs
+++ b/src/web/MZikmund.Web.Core/Mappings/CategoryMap.cs
@@ -9,5 +9,6 @@ public class CatgegoryMap : Profile
public CatgegoryMap()
{
CreateMap();
+ CreateMap();
}
}
diff --git a/src/web/MZikmund.Web.Core/Mappings/TagMap.cs b/src/web/MZikmund.Web.Core/Mappings/TagMap.cs
index d709434d..9c8459d9 100644
--- a/src/web/MZikmund.Web.Core/Mappings/TagMap.cs
+++ b/src/web/MZikmund.Web.Core/Mappings/TagMap.cs
@@ -9,5 +9,6 @@ public class TagMap : Profile
public TagMap()
{
CreateMap();
+ CreateMap();
}
}
diff --git a/src/web/MZikmund.Web/Controllers/TestController.cs b/src/web/MZikmund.Web/Controllers/TestController.cs
new file mode 100644
index 00000000..f1e3c7b2
--- /dev/null
+++ b/src/web/MZikmund.Web/Controllers/TestController.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Identity.Web;
+using Microsoft.Identity.Web.Resource;
+
+namespace MZikmund.Web.Controllers;
+
+[ApiController]
+[Authorize]
+[Route("api/v1/test")]
+public class TestController
+{
+ [HttpGet]
+ [Route("")]
+ public IActionResult Get() => new OkObjectResult("Hello world!");
+}
diff --git a/src/web/MZikmund.Web/Program.cs b/src/web/MZikmund.Web/Program.cs
index 6abccedd..a2bc7935 100644
--- a/src/web/MZikmund.Web/Program.cs
+++ b/src/web/MZikmund.Web/Program.cs
@@ -44,6 +44,7 @@
}
else
{
+ app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
@@ -56,10 +57,11 @@
app.UseRouting();
app.UseCors();
+app.MapHealthChecks("/health");
+app.UseAuthentication();
app.UseAuthorization();
-app.MapHealthChecks("/health");
app.MapControllers();
app.MapRazorPages();
@@ -99,7 +101,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration
services.AddLocalization(options => options.ResourcesPath = "Resources");
- services.AddControllers(options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()))
+ services.AddControllers()
.AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
services.AddEndpointsApiExplorer();
diff --git a/src/web/MZikmund.Web/appsettings.Development.json b/src/web/MZikmund.Web/appsettings.Development.json
index 0f4306ba..d12eda52 100644
--- a/src/web/MZikmund.Web/appsettings.Development.json
+++ b/src/web/MZikmund.Web/appsettings.Development.json
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
- "DatabaseConnection": "Server=(localdb)\\MSSQLLocalDB;Database=MZikmundDb;Trusted_Connection=True;"
+ "DatabaseConnection": "Server=(localdb)\\MSSQLLocalDB;Database=MZikmundDb"
},
"DetailedErrors": true,
"Logging": {