From dad7512e5c40ef068ec2406a7cd506eef707379e Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Jan 2025 11:22:05 -0500 Subject: [PATCH 01/41] reboot in .NET only Core and FunctionLight for now --- ...Cloud5mins.ShortenerTools.Functions.csproj | 0 .../Functions/UrlArchive.cs | 0 .../Functions/UrlClickStatsByDay.cs | 0 .../Functions/UrlCreate.cs | 0 .../Functions/UrlList.cs | 0 .../Functions/UrlRedirect.cs | 0 .../Functions/UrlUpdate.cs | 0 .../Program.cs | 0 .../Utility.cs | 0 .../host.json | 0 .../local.settings.example.json | 0 .../App.razor | 0 ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 0 .../Pages/Authentication.razor | 0 .../Pages/Help.razor | 0 .../Pages/Index.razor | 0 .../Pages/Statistics.razor | 0 .../Pages/UrlManager.razor | 0 .../Program.cs | 0 .../Properties/launchSettings.json | 0 .../README.md | 0 .../Shared/401.razor | 0 .../Shared/404.razor | 0 .../Shared/Custom401.razor | 0 .../Shared/Custom404.razor | 0 .../Shared/MainLayout.razor | 0 .../Shared/MainLayout.razor.css | 0 .../Shared/NavMenu.razor | 0 .../Shared/NavMenu.razor.css | 0 .../Shared/RedirectToLogin.razor | 0 .../Shared/ScheduleComponent.razor | 0 .../Shared/SchedulesComponent.razor | 0 .../_Imports.razor | 0 .../staticwebapp.config.json | 0 .../wwwroot/appsettings.example.json | 0 .../wwwroot/appsettings.json | 0 .../wwwroot/css/app.css | 0 .../wwwroot/css/bootstrap/bootstrap.min.css | 0 .../css/bootstrap/bootstrap.min.css.map | 0 .../wwwroot/css/open-iconic/FONT-LICENSE | 0 .../wwwroot/css/open-iconic/ICON-LICENSE | 0 .../wwwroot/css/open-iconic/README.md | 0 .../font/css/open-iconic-bootstrap.min.css | 0 .../open-iconic/font/fonts/open-iconic.eot | Bin .../open-iconic/font/fonts/open-iconic.otf | Bin .../open-iconic/font/fonts/open-iconic.svg | 0 .../open-iconic/font/fonts/open-iconic.ttf | Bin .../open-iconic/font/fonts/open-iconic.woff | Bin .../wwwroot/favicon-16x16.png | Bin .../wwwroot/favicon-32x32.png | Bin .../wwwroot/favicon.ico | Bin .../wwwroot/images/TinyBlazorAdmin.png | Bin .../wwwroot/index.html | 0 {src => src-old}/deployment/azureDeploy.json | 0 .../deployment/azureDeploy.params.dev.json | 0 .../deployment/azureDeploy.params.json | 0 {src => src-old}/deployment/debug.azcli | 0 {src => src-old}/deployment/debugARM.json | 0 ...Shortener (Local).postman_environment.json | 0 .../UrlShortener.postman_collection.json | 0 src/.devcontainer/Dockerfile | 20 -- src/.devcontainer/devcontainer.json | 24 --- src/.vscode/extensions.json | 6 - src/.vscode/launch.json | 32 --- src/.vscode/tasks.json | 117 ----------- src/AzUrlShortener.sln | 28 +++ .../Cloud5mins.ShortenerTools.Core.csproj | 5 +- .../Service/ShortenerToolException.cs | 16 ++ .../Service/UrlServices.cs | 196 ++++++++++++++++++ .../Service/Utility.cs | 70 +++++++ ...5mins.ShortenerTools.FunctionsLight.csproj | 34 +++ .../Functions/UrlRedirect.cs | 44 ++++ .../Program.cs | 19 ++ .../host.json | 16 ++ src/shortenerTools.sln | 37 ---- src/swa-cli.config.json | 13 -- src/tools/swa-local-simulator.sh | 1 - 77 files changed, 426 insertions(+), 252 deletions(-) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Program.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/Utility.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/host.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.Functions/local.settings.example.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png (100%) rename {src => src-old}/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html (100%) rename {src => src-old}/deployment/azureDeploy.json (100%) rename {src => src-old}/deployment/azureDeploy.params.dev.json (100%) rename {src => src-old}/deployment/azureDeploy.params.json (100%) rename {src => src-old}/deployment/debug.azcli (100%) rename {src => src-old}/deployment/debugARM.json (100%) rename {src => src-old}/tools/UrlShortener (Local).postman_environment.json (100%) rename {src => src-old}/tools/UrlShortener.postman_collection.json (100%) delete mode 100644 src/.devcontainer/Dockerfile delete mode 100644 src/.devcontainer/devcontainer.json delete mode 100644 src/.vscode/extensions.json delete mode 100644 src/.vscode/launch.json delete mode 100644 src/.vscode/tasks.json create mode 100644 src/AzUrlShortener.sln create mode 100644 src/Cloud5mins.ShortenerTools.Core/Service/ShortenerToolException.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs create mode 100644 src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj create mode 100644 src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs create mode 100644 src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs create mode 100644 src/Cloud5mins.ShortenerTools.FunctionsLight/host.json delete mode 100644 src/shortenerTools.sln delete mode 100644 src/swa-cli.config.json delete mode 100644 src/tools/swa-local-simulator.sh diff --git a/src/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj b/src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj rename to src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Program.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Program.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Program.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Program.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/Utility.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/Utility.cs rename to src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs diff --git a/src/Cloud5mins.ShortenerTools.Functions/host.json b/src-old/Cloud5mins.ShortenerTools.Functions/host.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/host.json rename to src-old/Cloud5mins.ShortenerTools.Functions/host.json diff --git a/src/Cloud5mins.ShortenerTools.Functions/local.settings.example.json b/src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.Functions/local.settings.example.json rename to src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html similarity index 100% rename from src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html rename to src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html diff --git a/src/deployment/azureDeploy.json b/src-old/deployment/azureDeploy.json similarity index 100% rename from src/deployment/azureDeploy.json rename to src-old/deployment/azureDeploy.json diff --git a/src/deployment/azureDeploy.params.dev.json b/src-old/deployment/azureDeploy.params.dev.json similarity index 100% rename from src/deployment/azureDeploy.params.dev.json rename to src-old/deployment/azureDeploy.params.dev.json diff --git a/src/deployment/azureDeploy.params.json b/src-old/deployment/azureDeploy.params.json similarity index 100% rename from src/deployment/azureDeploy.params.json rename to src-old/deployment/azureDeploy.params.json diff --git a/src/deployment/debug.azcli b/src-old/deployment/debug.azcli similarity index 100% rename from src/deployment/debug.azcli rename to src-old/deployment/debug.azcli diff --git a/src/deployment/debugARM.json b/src-old/deployment/debugARM.json similarity index 100% rename from src/deployment/debugARM.json rename to src-old/deployment/debugARM.json diff --git a/src/tools/UrlShortener (Local).postman_environment.json b/src-old/tools/UrlShortener (Local).postman_environment.json similarity index 100% rename from src/tools/UrlShortener (Local).postman_environment.json rename to src-old/tools/UrlShortener (Local).postman_environment.json diff --git a/src/tools/UrlShortener.postman_collection.json b/src-old/tools/UrlShortener.postman_collection.json similarity index 100% rename from src/tools/UrlShortener.postman_collection.json rename to src-old/tools/UrlShortener.postman_collection.json diff --git a/src/.devcontainer/Dockerfile b/src/.devcontainer/Dockerfile deleted file mode 100644 index 18d2a84f1..000000000 --- a/src/.devcontainer/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# Find the Dockerfile at this URL -# https://github.com/Azure/azure-functions-docker/blob/dev/host/4/bullseye/amd64/dotnet/dotnet-isolated/dotnet-isolated-core-tools.Dockerfile -FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated6.0-core-tools - -# Uncomment following lines If you want to enable Development Container Script -# For more details https://github.com/microsoft/vscode-dev-containers/tree/main/script-library - -# Avoid warnings by switching to noninteractive -# ENV DEBIAN_FRONTEND=noninteractive - -# # Comment out these lines if you want to use zsh. - -ARG INSTALL_ZSH=true -ARG USERNAME=vscode -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN apt-get update && curl -ssL https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/common-debian.sh -o /tmp/common-script.sh \ - && /bin/bash /tmp/common-script.sh "$INSTALL_ZSH" "$USERNAME" "$USER_UID" "$USER_GID" \ - && rm /tmp/common-script.sh \ No newline at end of file diff --git a/src/.devcontainer/devcontainer.json b/src/.devcontainer/devcontainer.json deleted file mode 100644 index e9af2dbb7..000000000 --- a/src/.devcontainer/devcontainer.json +++ /dev/null @@ -1,24 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/azure-functions-dotnet-6-isolated -{ - "name": "Azure Functions & C# - .NET 6 (Isolated)", - "dockerFile": "Dockerfile", - "forwardPorts": [ 7071 ], - - // Set *default* container specific settings.json values on container create. - "settings": {}, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-azuretools.vscode-azurefunctions", - "ms-dotnettools.csharp", - "eamodio.gitlens", - "ms-vscode.azurecli" - ], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "dotnet restore", - - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" -} diff --git a/src/.vscode/extensions.json b/src/.vscode/extensions.json deleted file mode 100644 index de991f40a..000000000 --- a/src/.vscode/extensions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recommendations": [ - "ms-azuretools.vscode-azurefunctions", - "ms-dotnettools.csharp" - ] -} diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json deleted file mode 100644 index 33df042a3..000000000 --- a/src/.vscode/launch.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Attach to .NET Functions", - "type": "coreclr", - "request": "attach", - "processId": "${command:azureFunctions.pickProcess}" - }, - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/bin/Debug/net6.0/shortenerTools.dll", - "args": [], - "cwd": "${workspaceFolder}/src", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} diff --git a/src/.vscode/tasks.json b/src/.vscode/tasks.json deleted file mode 100644 index ebf5724b1..000000000 --- a/src/.vscode/tasks.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "clean (functions)", - "command": "dotnet", - "args": [ - "clean", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "type": "process", - "problemMatcher": "$msCompile", - "options": { - "cwd": "${workspaceFolder}/src" - } - }, - { - "label": "build (functions)", - "command": "dotnet", - "args": [ - "build", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "type": "process", - "dependsOn": "clean (functions)", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": "$msCompile", - "options": { - "cwd": "${workspaceFolder}/src" - } - }, - { - "label": "clean release (functions)", - "command": "dotnet", - "args": [ - "clean", - "--configuration", - "Release", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "type": "process", - "problemMatcher": "$msCompile", - "options": { - "cwd": "${workspaceFolder}/src" - } - }, - { - "label": "publish (functions)", - "command": "dotnet", - "args": [ - "publish", - "--configuration", - "Release", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "type": "process", - "dependsOn": "clean release (functions)", - "problemMatcher": "$msCompile", - "options": { - "cwd": "${workspaceFolder}/src" - } - }, - { - "type": "func", - "dependsOn": "build (functions)", - "options": { - "cwd": "${workspaceFolder}/src/bin/Debug/net6.0" - }, - "command": "host start", - "isBackground": true, - "problemMatcher": "$func-dotnet-watch" - }, - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/src/shortenerTools.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/src/shortenerTools.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/src/shortenerTools.csproj" - ], - "problemMatcher": "$msCompile" - } - ] -} diff --git a/src/AzUrlShortener.sln b/src/AzUrlShortener.sln new file mode 100644 index 000000000..f2744b0cc --- /dev/null +++ b/src/AzUrlShortener.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.Core", "Cloud5mins.ShortenerTools.Core\Cloud5mins.ShortenerTools.Core.csproj", "{99DF620C-9FF5-4233-B556-E5E9E143AB60}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.FunctionsLight", "Cloud5mins.ShortenerTools.FunctionsLight\Cloud5mins.ShortenerTools.FunctionsLight.csproj", "{1C59956F-01A4-4F22-8B75-39CC48A8290C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99DF620C-9FF5-4233-B556-E5E9E143AB60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99DF620C-9FF5-4233-B556-E5E9E143AB60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99DF620C-9FF5-4233-B556-E5E9E143AB60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99DF620C-9FF5-4233-B556-E5E9E143AB60}.Release|Any CPU.Build.0 = Release|Any CPU + {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj b/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj index 49c78842c..4181074b4 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj +++ b/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj @@ -1,11 +1,12 @@ - net8.0 + net9.0 enable enable - + + \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/ShortenerToolException.cs b/src/Cloud5mins.ShortenerTools.Core/Service/ShortenerToolException.cs new file mode 100644 index 000000000..9d27c4d88 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Service/ShortenerToolException.cs @@ -0,0 +1,16 @@ +using System.Net; + +namespace Cloud5mins.ShortenerTools.Core.Services; + +public class ShortenerToolException: Exception +{ + public HttpStatusCode StatusCode { get; set; } + public ShortenerToolException(string message) : base(message) + { + } + + public ShortenerToolException(HttpStatusCode statusCode, string message) : base(message) + { + StatusCode = statusCode; + } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs new file mode 100644 index 000000000..89f695e6c --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs @@ -0,0 +1,196 @@ +using Cloud5mins.ShortenerTools; +using Cloud5mins.ShortenerTools.Core.Domain; +using Cloud5mins.ShortenerTools.Core.Messages; +using Microsoft.Extensions.Logging; +using System.Net; + +namespace Cloud5mins.ShortenerTools.Core.Services; + +public class UrlServices +{ + private readonly ShortenerSettings _settings; + private readonly ILogger _logger; + private readonly StorageTableHelper _stgHelper; + + public UrlServices(ShortenerSettings settings, ILogger logger) + { + _settings = settings; + _logger = logger; + } + + private StorageTableHelper StgHelper => _stgHelper ?? new StorageTableHelper(_settings.DataStorage); + + public async Task Archive(ShortUrlEntity input) + { + ShortUrlEntity result = await _stgHelper.ArchiveShortUrlEntity(input); + return result; + } + public async Task Redirect(string shortUrl) + { + string redirectUrl = "https://azure.com"; + try + { + if (!string.IsNullOrWhiteSpace(shortUrl)) + { + redirectUrl = _settings.DefaultRedirectUrl ?? redirectUrl; + + var tempUrl = new ShortUrlEntity(string.Empty, shortUrl); + var newUrl = await StgHelper.GetShortUrlEntity(tempUrl); + + if (newUrl != null) + { + _logger.LogInformation($"Found it: {newUrl.Url}"); + newUrl.Clicks++; + await StgHelper.SaveClickStatsEntity(new ClickStatsEntity(newUrl.RowKey)); + await StgHelper.SaveShortUrlEntity(newUrl); + redirectUrl = WebUtility.UrlDecode(newUrl.ActiveUrl); + } + } + else + { + _logger.LogInformation("Bad Link, resorting to fallback."); + } + } + catch (Exception ex) + { + _logger.LogInformation($"Problem accessing storage: {ex.Message}"); + } + return redirectUrl; + } + + public async Task List(string host) + { + _logger.LogInformation($"Starting UrlList..."); + + var result = new ListResponse(); + string userId = string.Empty; + + try + { + result.UrlList = await StgHelper.GetAllShortUrlEntities(); + result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); + foreach (ShortUrlEntity url in result.UrlList) + { + url.ShortUrl = Utility.GetShortUrl(host, url.RowKey); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "An unexpected error was encountered."); + throw; + } + + return result; + } + + public async Task Create(ShortRequest input, string host) + { + ShortResponse result; + + try + { + + // If the Url parameter only contains whitespaces or is empty return with BadRequest. + if (string.IsNullOrWhiteSpace(input.Url)) + { + throw new ShortenerToolException(HttpStatusCode.BadRequest, "The url parameter can not be empty."); + } + + // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com + if (!Uri.IsWellFormedUriString(input.Url, UriKind.Absolute)) + { + throw new ShortenerToolException(HttpStatusCode.BadRequest, $"{input.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'."); + } + + string longUrl = input.Url.Trim(); + string vanity = string.IsNullOrWhiteSpace(input.Vanity) ? "" : input.Vanity.Trim(); + string title = string.IsNullOrWhiteSpace(input.Title) ? "" : input.Title.Trim(); + + ShortUrlEntity newRow; + + if (!string.IsNullOrEmpty(vanity)) + { + newRow = new ShortUrlEntity(longUrl, vanity, title, input.Schedules); + + if (await StgHelper.IfShortUrlEntityExist(newRow)) + { + throw new ShortenerToolException(HttpStatusCode.Conflict, "This Short URL already exist."); + } + } + else + { + newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, StgHelper), title, input.Schedules); + } + + await StgHelper.SaveShortUrlEntity(newRow); + + result = new ShortResponse(host, newRow.Url, newRow.RowKey, newRow.Title); + + _logger.LogInformation("Short Url created."); + } + catch (Exception ex) + { + _logger.LogError(ex, "An unexpected error was encountered."); + throw; + } + + return result; + } + + public async Task Update(ShortUrlEntity input, string host) + { + ShortUrlEntity result; + + try + { + // If the Url parameter only contains whitespaces or is empty return with BadRequest. + if (string.IsNullOrWhiteSpace(input.Url)) + { + throw new ShortenerToolException(HttpStatusCode.BadRequest, "The url parameter can not be empty."); + } + + // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com + if (!Uri.IsWellFormedUriString(input.Url, UriKind.Absolute)) + { + throw new ShortenerToolException(HttpStatusCode.BadRequest, $"{input.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'."); + } + + result = await StgHelper.UpdateShortUrlEntity(input); + result.ShortUrl = Utility.GetShortUrl(host, result.RowKey); + + } + catch (Exception ex) + { + _logger.LogError(ex, "An unexpected error was encountered."); + throw; + } + + return result; + } + + + public async Task ClickStatsByDay(UrlClickStatsRequest input, string host) + { + var result = new ClickDateList(); + try + { + var rawStats = await StgHelper.GetAllStatsByVanity(input.Vanity); + + result.Items = rawStats.GroupBy(s => DateTime.Parse(s.Datetime).Date) + .Select(stat => new ClickDate + { + DateClicked = stat.Key.ToString("yyyy-MM-dd"), + Count = stat.Count() + }).OrderBy(s => DateTime.Parse(s.DateClicked).Date).ToList(); + + result.Url = Utility.GetShortUrl(host, input.Vanity); + } + catch (Exception ex) + { + _logger.LogError(ex, "An unexpected error was encountered."); + throw; + } + return result; + } +} + diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs new file mode 100644 index 000000000..3bbd7e1e0 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs @@ -0,0 +1,70 @@ +using Cloud5mins.ShortenerTools.Core.Domain; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Threading.Tasks; + + + +namespace Cloud5mins.ShortenerTools +{ + public static class Utility + { + //reshuffled for randomisation, same unique characters just jumbled up, you can replace with your own version + private const string ConversionCode = "FjTG0s5dgWkbLf_8etOZqMzNhmp7u6lUJoXIDiQB9-wRxCKyrPcv4En3Y21aASHV"; + private static readonly int Base = ConversionCode.Length; + //sets the length of the unique code to add to vanity + private const int MinVanityCodeLength = 5; + + public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) + { + if (string.IsNullOrEmpty(vanity)) + { + var newKey = await stgHelper.GetNextTableId(); + string getCode() => Encode(newKey); + if (await stgHelper.IfShortUrlEntityExistByVanity(getCode())) + return await GetValidEndUrl(vanity, stgHelper); + + return string.Join(string.Empty, getCode()); + } + else + { + return string.Join(string.Empty, vanity); + } + } + + public static string Encode(int i) + { + if (i == 0) + return ConversionCode[0].ToString(); + + return GenerateUniqueRandomToken(i); + } + + public static string GetShortUrl(string host, string vanity) + { + return host + "/" + vanity; + } + + // generates a unique, random, and alphanumeric token for the use as a url + //(not entirely secure but not sequential so generally not guessable) + public static string GenerateUniqueRandomToken(int uniqueId) + { + using (var generator = RandomNumberGenerator.Create()) + { + //minimum size I would suggest is 5, longer the better but we want short URLs! + var bytes = new byte[MinVanityCodeLength]; + generator.GetBytes(bytes); + var chars = bytes + .Select(b => ConversionCode[b % ConversionCode.Length]); + var token = new string(chars.ToArray()); + var reversedToken = string.Join(string.Empty, token.Reverse()); + return uniqueId + reversedToken; + } + } + } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj b/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj new file mode 100644 index 000000000..c7d841908 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj @@ -0,0 +1,34 @@ + + + net9.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs new file mode 100644 index 000000000..f4759da23 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs @@ -0,0 +1,44 @@ +using Cloud5mins.ShortenerTools.Core.Services; +using Cloud5mins.ShortenerTools.Core.Domain; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace Cloud5mins.ShortenerTools.Functions +{ + public class UrlRedirect + { + private readonly ILogger _logger; + private readonly ShortenerSettings _settings; + + public UrlRedirect(ILoggerFactory loggerFactory, ShortenerSettings settings) + { + _logger = loggerFactory.CreateLogger(); + _logger.LogInformation("UrlRedirect in constructor"); + _settings = settings; + } + + [Function("UrlRedirect")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "{shortUrl}")] + HttpRequestData req, + string shortUrl, + ExecutionContext context) + { + _logger.LogInformation("Function reached"); + UrlServices UrlServices = new UrlServices(_settings, _logger); + _logger.LogInformation("Services created"); + _logger.LogInformation($"Redirecting {shortUrl}"); + string redirectUrl = await UrlServices.Redirect(shortUrl); + _logger.LogInformation("got the redirect url"); + var res = req.CreateResponse(HttpStatusCode.Redirect); + res.Headers.Add("Location", redirectUrl); + _logger.LogInformation("response created"); + return res; + + } + } +} diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs new file mode 100644 index 000000000..91876cad9 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Cloud5mins.ShortenerTools.Core.Domain; + +var builder = FunctionsApplication.CreateBuilder(args); + +// Bind configuration to ShortenerSettings +builder.Services.Configure(builder.Configuration.GetSection("Values")); + +builder.ConfigureFunctionsWebApplication(); + +// Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4. +// builder.Services +// .AddApplicationInsightsTelemetryWorkerService() +// .ConfigureFunctionsApplicationInsights(); + +builder.Build().Run(); \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/host.json b/src/Cloud5mins.ShortenerTools.FunctionsLight/host.json new file mode 100644 index 000000000..c13ccb7b1 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/host.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensions": { + "http": { + "routePrefix": "" + } + } +} \ No newline at end of file diff --git a/src/shortenerTools.sln b/src/shortenerTools.sln deleted file mode 100644 index 24bd5b780..000000000 --- a/src/shortenerTools.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33213.308 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud5mins.ShortenerTools.Functions", "Cloud5mins.ShortenerTools.Functions\Cloud5mins.ShortenerTools.Functions.csproj", "{6388276F-17B7-4AB0-9A6B-C0E2F4A3D283}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud5mins.ShortenerTools.Core", "Cloud5mins.ShortenerTools.Core\Cloud5mins.ShortenerTools.Core.csproj", "{7449639F-62CD-4B2C-AB98-58C85904FDC4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cloud5mins.ShortenerTools.TinyBlazorAdmin", "Cloud5mins.ShortenerTools.TinyBlazorAdmin\Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj", "{B5FB0C0A-05BA-491B-9379-CE4BDF025B3B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6388276F-17B7-4AB0-9A6B-C0E2F4A3D283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6388276F-17B7-4AB0-9A6B-C0E2F4A3D283}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6388276F-17B7-4AB0-9A6B-C0E2F4A3D283}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6388276F-17B7-4AB0-9A6B-C0E2F4A3D283}.Release|Any CPU.Build.0 = Release|Any CPU - {7449639F-62CD-4B2C-AB98-58C85904FDC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7449639F-62CD-4B2C-AB98-58C85904FDC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7449639F-62CD-4B2C-AB98-58C85904FDC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7449639F-62CD-4B2C-AB98-58C85904FDC4}.Release|Any CPU.Build.0 = Release|Any CPU - {B5FB0C0A-05BA-491B-9379-CE4BDF025B3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5FB0C0A-05BA-491B-9379-CE4BDF025B3B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5FB0C0A-05BA-491B-9379-CE4BDF025B3B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5FB0C0A-05BA-491B-9379-CE4BDF025B3B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {24E0DF25-6719-4352-AD75-BA5BD982ECF8} - EndGlobalSection -EndGlobal diff --git a/src/swa-cli.config.json b/src/swa-cli.config.json deleted file mode 100644 index 76645e64f..000000000 --- a/src/swa-cli.config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", - "configurations": { - "AzUrlShortener": { - "appLocation": "Cloud5mins.ShortenerTools.TinyBlazorAdmin", - "apiLocation": "Cloud5mins.ShortenerTools.Functions", - "outputLocation": "bin/Debug/net6.0/wwwroot", - "appBuildCommand": "dotnet publish -c Release -o bin", - "apiBuildCommand": "dotnet publish -c Release", - "run": "dotnet watch run" - } - } -} \ No newline at end of file diff --git a/src/tools/swa-local-simulator.sh b/src/tools/swa-local-simulator.sh deleted file mode 100644 index eba263e86..000000000 --- a/src/tools/swa-local-simulator.sh +++ /dev/null @@ -1 +0,0 @@ -swa start http://localhost:5000 --api-location http://localhost:7071 From 26910df4c21a3a998ec666cda412982431612bde Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Jan 2025 15:54:56 -0500 Subject: [PATCH 02/41] Add .NET Aspire and use Environment Variable instead of ShortenerSettings --- src/AzUrlShortener.sln | 12 ++ .../Cloud5mins.ShortenerTools.AppHost.csproj | 22 ++++ .../Program.cs | 9 ++ .../Properties/launchSettings.json | 29 +++++ .../appsettings.json | 9 ++ .../Service/UrlServices.cs | 8 +- ...5mins.ShortenerTools.FunctionsLight.csproj | 1 + .../Functions/UrlRedirect.cs | 7 +- .../Program.cs | 6 - ...mins.ShortenerTools.ServiceDefaults.csproj | 22 ++++ .../Extensions.cs | 118 ++++++++++++++++++ 11 files changed, 228 insertions(+), 15 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj create mode 100644 src/Cloud5mins.ShortenerTools.AppHost/Program.cs create mode 100644 src/Cloud5mins.ShortenerTools.AppHost/Properties/launchSettings.json create mode 100644 src/Cloud5mins.ShortenerTools.AppHost/appsettings.json create mode 100644 src/Cloud5mins.ShortenerTools.ServiceDefaults/Cloud5mins.ShortenerTools.ServiceDefaults.csproj create mode 100644 src/Cloud5mins.ShortenerTools.ServiceDefaults/Extensions.cs diff --git a/src/AzUrlShortener.sln b/src/AzUrlShortener.sln index f2744b0cc..0319a4788 100644 --- a/src/AzUrlShortener.sln +++ b/src/AzUrlShortener.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.FunctionsLight", "Cloud5mins.ShortenerTools.FunctionsLight\Cloud5mins.ShortenerTools.FunctionsLight.csproj", "{1C59956F-01A4-4F22-8B75-39CC48A8290C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.AppHost", "Cloud5mins.ShortenerTools.AppHost\Cloud5mins.ShortenerTools.AppHost.csproj", "{3DC7E590-FE84-4D0B-B989-9CD6B31B325A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.ServiceDefaults", "Cloud5mins.ShortenerTools.ServiceDefaults\Cloud5mins.ShortenerTools.ServiceDefaults.csproj", "{6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +28,13 @@ Global {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C59956F-01A4-4F22-8B75-39CC48A8290C}.Release|Any CPU.Build.0 = Release|Any CPU + {3DC7E590-FE84-4D0B-B989-9CD6B31B325A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DC7E590-FE84-4D0B-B989-9CD6B31B325A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DC7E590-FE84-4D0B-B989-9CD6B31B325A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DC7E590-FE84-4D0B-B989-9CD6B31B325A}.Release|Any CPU.Build.0 = Release|Any CPU + {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj new file mode 100644 index 000000000..57a10da16 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + true + 7de80ffa-2cbf-4a48-aa38-16d6b97e09bf + + + + + + + + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs new file mode 100644 index 000000000..230160df8 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs @@ -0,0 +1,9 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var urlData = builder.AddAzureStorage("urlData"); + +var azFuncLight = builder.AddAzureFunctionsProject("azFuncLight") + .WithHostStorage(urlData) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Properties/launchSettings.json b/src/Cloud5mins.ShortenerTools.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..5042b8cca --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17064;http://localhost:15014", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21278", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22155" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15014", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19258", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20204" + } + } + } +} diff --git a/src/Cloud5mins.ShortenerTools.AppHost/appsettings.json b/src/Cloud5mins.ShortenerTools.AppHost/appsettings.json new file mode 100644 index 000000000..31c092aa4 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs index 89f695e6c..cc93976e9 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs @@ -8,17 +8,15 @@ namespace Cloud5mins.ShortenerTools.Core.Services; public class UrlServices { - private readonly ShortenerSettings _settings; private readonly ILogger _logger; private readonly StorageTableHelper _stgHelper; - public UrlServices(ShortenerSettings settings, ILogger logger) + public UrlServices(ILogger logger) { - _settings = settings; _logger = logger; } - private StorageTableHelper StgHelper => _stgHelper ?? new StorageTableHelper(_settings.DataStorage); + private StorageTableHelper StgHelper => _stgHelper ?? new StorageTableHelper(Environment.GetEnvironmentVariable("DataStorage")!); public async Task Archive(ShortUrlEntity input) { @@ -32,7 +30,7 @@ public async Task Redirect(string shortUrl) { if (!string.IsNullOrWhiteSpace(shortUrl)) { - redirectUrl = _settings.DefaultRedirectUrl ?? redirectUrl; + redirectUrl = Environment.GetEnvironmentVariable("DefaultRedirectUrl") ?? redirectUrl; var tempUrl = new ShortUrlEntity(string.Empty, shortUrl); var newUrl = await StgHelper.GetShortUrlEntity(tempUrl); diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj b/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj index c7d841908..ee9d2c679 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Cloud5mins.ShortenerTools.FunctionsLight.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs index f4759da23..6af27ac1c 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs @@ -12,13 +12,12 @@ namespace Cloud5mins.ShortenerTools.Functions public class UrlRedirect { private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - public UrlRedirect(ILoggerFactory loggerFactory, ShortenerSettings settings) + + public UrlRedirect(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); _logger.LogInformation("UrlRedirect in constructor"); - _settings = settings; } [Function("UrlRedirect")] @@ -29,7 +28,7 @@ public async Task Run( ExecutionContext context) { _logger.LogInformation("Function reached"); - UrlServices UrlServices = new UrlServices(_settings, _logger); + UrlServices UrlServices = new UrlServices(_logger); _logger.LogInformation("Services created"); _logger.LogInformation($"Redirecting {shortUrl}"); string redirectUrl = await UrlServices.Redirect(shortUrl); diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs index 91876cad9..454d85200 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs @@ -1,14 +1,8 @@ using Microsoft.Azure.Functions.Worker.Builder; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Cloud5mins.ShortenerTools.Core.Domain; var builder = FunctionsApplication.CreateBuilder(args); -// Bind configuration to ShortenerSettings -builder.Services.Configure(builder.Configuration.GetSection("Values")); - builder.ConfigureFunctionsWebApplication(); // Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4. diff --git a/src/Cloud5mins.ShortenerTools.ServiceDefaults/Cloud5mins.ShortenerTools.ServiceDefaults.csproj b/src/Cloud5mins.ShortenerTools.ServiceDefaults/Cloud5mins.ShortenerTools.ServiceDefaults.csproj new file mode 100644 index 000000000..9f4d04856 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.ServiceDefaults/Cloud5mins.ShortenerTools.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.ServiceDefaults/Extensions.cs b/src/Cloud5mins.ShortenerTools.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..2a3f4e074 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.ServiceDefaults/Extensions.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} From 5587b67e4a705da12e5c9a0efb67fd357be84c41 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Jan 2025 17:38:58 -0500 Subject: [PATCH 03/41] adding vanilla api --- src/AzUrlShortener.sln | 6 +++ .../Cloud5mins.ShortenerTools.Api.csproj | 13 ++++++ .../Cloud5mins.ShortenerTools.Api.http | 6 +++ src/Cloud5mins.ShortenerTools.Api/Program.cs | 46 +++++++++++++++++++ .../Properties/launchSettings.json | 23 ++++++++++ .../appsettings.json | 9 ++++ 6 files changed, 103 insertions(+) create mode 100644 src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj create mode 100644 src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http create mode 100644 src/Cloud5mins.ShortenerTools.Api/Program.cs create mode 100644 src/Cloud5mins.ShortenerTools.Api/Properties/launchSettings.json create mode 100644 src/Cloud5mins.ShortenerTools.Api/appsettings.json diff --git a/src/AzUrlShortener.sln b/src/AzUrlShortener.sln index 0319a4788..81f3998bc 100644 --- a/src/AzUrlShortener.sln +++ b/src/AzUrlShortener.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.ServiceDefaults", "Cloud5mins.ShortenerTools.ServiceDefaults\Cloud5mins.ShortenerTools.ServiceDefaults.csproj", "{6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.Api", "Cloud5mins.ShortenerTools.Api\Cloud5mins.ShortenerTools.Api.csproj", "{85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,5 +38,9 @@ Global {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Release|Any CPU.ActiveCfg = Release|Any CPU {6567F739-8BCC-4B4B-8CCC-EEEB9B28465F}.Release|Any CPU.Build.0 = Release|Any CPU + {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj new file mode 100644 index 000000000..ee25c2ffa --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http new file mode 100644 index 000000000..888ae0a66 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -0,0 +1,6 @@ +@Cloud5mins.ShortenerTools.Api_HostAddress = http://localhost:5288 + +GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/Cloud5mins.ShortenerTools.Api/Program.cs b/src/Cloud5mins.ShortenerTools.Api/Program.cs new file mode 100644 index 000000000..fe4b44d25 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/Program.cs @@ -0,0 +1,46 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast"); + + + + + + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/src/Cloud5mins.ShortenerTools.Api/Properties/launchSettings.json b/src/Cloud5mins.ShortenerTools.Api/Properties/launchSettings.json new file mode 100644 index 000000000..f912b6622 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5288", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7187;http://localhost:5288", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Cloud5mins.ShortenerTools.Api/appsettings.json b/src/Cloud5mins.ShortenerTools.Api/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From a63ad6b6135ab72d5d6929cc2000cd8f701bfe93 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Jan 2025 16:16:02 -0500 Subject: [PATCH 04/41] UrlCreate migrated --- .../Cloud5mins.ShortenerTools.Api.csproj | 5 + .../Cloud5mins.ShortenerTools.Api.http | 45 +++++++- src/Cloud5mins.ShortenerTools.Api/Program.cs | 47 ++++---- .../ShortenerEnpoints.cs | 106 ++++++++++++++++++ .../WeatherService.cs | 25 +++++ .../Cloud5mins.ShortenerTools.AppHost.csproj | 1 + .../Program.cs | 18 ++- .../Domain/DetailledBadRequest.cs | 7 ++ .../Domain/IStorageTableHelper.cs | 20 ++++ .../Domain/StorageTableHelper.cs | 2 +- .../Service/Utility.cs | 2 +- 11 files changed, 245 insertions(+), 33 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs create mode 100644 src/Cloud5mins.ShortenerTools.Api/WeatherService.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/DetailledBadRequest.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/IStorageTableHelper.cs diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj index ee25c2ffa..035095b98 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj @@ -10,4 +10,9 @@ + + + + + diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http index 888ae0a66..87dffd4fd 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -1,6 +1,47 @@ -@Cloud5mins.ShortenerTools.Api_HostAddress = http://localhost:5288 +## @Cloud5mins.ShortenerTools.Api_HostAddress = http://localhost:5288 +@Cloud5mins.ShortenerTools.Api_HostAddress = https://localhost:7187 GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/weatherforecast/ Accept: application/json -### +### Get a Welcome message +Get {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api + + +### Create a new short URL + +POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +Accept: application/json +Content-Type: application/json + +{ + "Vanity": "1111-test", + "Url": "https://learn.microsoft.com/en-us/dotnet/aspire/storage/azure-storage-blobs-integration?tabs=dotnet-cli", + "Title": ".NET Aspire Azure Blob Storage integration" +} + +### ERROR no URL + +POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +Accept: application/json +Content-Type: application/json + +{ + "Vanity": "1111-test", + "Url": "", + "Title": ".NET Aspire Azure Blob Storage integration" +} + +### ERROR URL no valide + +POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +Accept: application/json +Content-Type: application/json + +{ + "Vanity": "1111-test", + "Url": "That's not a URL", + "Title": ".NET Aspire Azure Blob Storage integration" +} + + diff --git a/src/Cloud5mins.ShortenerTools.Api/Program.cs b/src/Cloud5mins.ShortenerTools.Api/Program.cs index fe4b44d25..e4658b360 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Program.cs +++ b/src/Cloud5mins.ShortenerTools.Api/Program.cs @@ -1,11 +1,29 @@ +using Cloud5mins.ShortenerTools.Core.Domain; + var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Add services to the container. // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); +string connStr = Environment.GetEnvironmentVariable("data-storage-connstr")?? string.Empty; + +builder.Services.AddTransient(sp => new StorageTableHelper(connStr)); +builder.Services.AddTransient(sp => +{ + var loggerFactory = sp.GetRequiredService(); + return loggerFactory.CreateLogger("shortenerLogger"); +}); + +//to remove just while migration +builder.Services.AddSingleton(); // Register WeatherService + var app = builder.Build(); +app.MapDefaultEndpoints(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -14,33 +32,10 @@ app.UseHttpsRedirection(); -var summaries = new[] -{ - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; - -app.MapGet("/weatherforecast", () => -{ - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; -}) -.WithName("GetWeatherForecast"); - - - - +app.MapShortenerEnpoints(); +app.MapGet("/weatherforecast", (WeatherService weatherService) => weatherService.GetWeatherForecast()) + .WithName("GetWeatherForecast"); app.Run(); -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} diff --git a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs new file mode 100644 index 000000000..b362b95b5 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs @@ -0,0 +1,106 @@ +using System.Data; +using System.Threading.Tasks.Dataflow; +using System.Xml.Linq; +using Cloud5mins.ShortenerTools; +using Cloud5mins.ShortenerTools.Core.Domain; +using Cloud5mins.ShortenerTools.Core.Messages; +using Microsoft.AspNetCore.Http.HttpResults; + +public static class ShortenerEnpoints +{ + public static void MapShortenerEnpoints(this IEndpointRouteBuilder app) + { + var endpoints = app.MapGroup("api") + .WithOpenApi(); + + endpoints.MapPost("/", GetWelcomeMessage) + .WithDescription("Welcome to Cloud5mins URL Shortener API"); + + endpoints.MapGet("/UrlCreate", UrlCreate) + .WithDescription("Create a new note") + .WithDisplayName("UrlCreate"); + + } + + static private string GetWelcomeMessage() + { + return "Welcome to Cloud5mins URL Shortener API"; + } + + static private async Task, + BadRequest, + NotFound, + Conflict, + InternalServerError + >> UrlCreate(ShortRequest request, + IStorageTableHelper stgHelper, + HttpContext context, + ILogger logger) + { + + logger.LogTrace($"creating shortURL: {request.Url}"); + var result = new ShortResponse(); + + try + { + // If the Url parameter only contains whitespaces or is empty return with BadRequest. + if (string.IsNullOrWhiteSpace(request.Url)) + { + string ErrorMsg = "The url parameter cannot be empty."; + logger.LogInformation(ErrorMsg); + return TypedResults.NotFound(new DetailedBadRequest { Message = ErrorMsg }); + } + + // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com + if (!Uri.IsWellFormedUriString(request.Url, UriKind.Absolute)) + { + string ErrorMsg = $"{request.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'."; + logger.LogInformation(ErrorMsg); + return TypedResults.BadRequest(new DetailedBadRequest { Message = ErrorMsg }); + } + + string longUrl = request.Url.Trim(); + string vanity = string.IsNullOrWhiteSpace(request.Vanity) ? "" : request.Vanity.Trim(); + string title = string.IsNullOrWhiteSpace(request.Title) ? "" : request.Title.Trim(); + + ShortUrlEntity? newRow; + + if (!string.IsNullOrEmpty(vanity)) + { + newRow = new ShortUrlEntity(longUrl, vanity, title, request.Schedules); + if (await stgHelper.IfShortUrlEntityExist(newRow)) + { + // throw new Exception("This Short URL already exist."); + string ErrorMsg = "This Short URL already exist."; + logger.LogInformation(ErrorMsg); + return TypedResults.Conflict(new DetailedBadRequest { Message = ErrorMsg }); + } + } + else + { + newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); + } + + await stgHelper.SaveShortUrlEntity(newRow); + + string? customDomain = Environment.GetEnvironmentVariable("CustomDomain"); + + + var host = string.IsNullOrEmpty(customDomain) ? context.Request.Host.Value : customDomain; + result = new ShortResponse(host!, newRow.Url, newRow.RowKey, newRow.Title); + + logger.LogTrace("Short Url created."); + + return TypedResults.Created($"/api/UrlCreate/{result.ShortUrl}", result); + } + catch (Exception ex) + { + Console.WriteLine($"A unexpected error was encountered: {ex.Message}"); + logger.LogError(ex.Message); + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); + } + } + +} + diff --git a/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs b/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs new file mode 100644 index 000000000..609a8e2e3 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs @@ -0,0 +1,25 @@ +public class WeatherService +{ + private static readonly string[] summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + public WeatherForecast[] GetWeatherForecast() + { + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; + } +} + +public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj index 57a10da16..3e6969a33 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj +++ b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs index 230160df8..f32531673 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs +++ b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs @@ -1,9 +1,21 @@ + var builder = DistributedApplication.CreateBuilder(args); -var urlData = builder.AddAzureStorage("urlData"); +var connectionString = builder.AddConnectionString("data-storage-connstr"); +var customDomain = builder.AddParameter("CustomDomain"); +var defaultRedirectUrl = builder.AddParameter("DefaultRedirectUrl"); + +//var urlData = builder.AddAzureStorage("url-data"); -var azFuncLight = builder.AddAzureFunctionsProject("azFuncLight") - .WithHostStorage(urlData) +var azFuncLight = builder.AddAzureFunctionsProject("azfunc-light") + //.WithHostStorage(urlData) .WithExternalHttpEndpoints(); +var manAPI = builder.AddProject("api") + .WithEnvironment("data-storage-connstr",connectionString) + .WithEnvironment("CustomDomain",customDomain) + .WithEnvironment("DefaultRedirectUrl",defaultRedirectUrl) + .WithExternalHttpEndpoints(); + + builder.Build().Run(); diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/DetailledBadRequest.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/DetailledBadRequest.cs new file mode 100644 index 000000000..8fd2326af --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/DetailledBadRequest.cs @@ -0,0 +1,7 @@ +namespace Cloud5mins.ShortenerTools.Core.Domain +{ + public class DetailedBadRequest + { + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/IStorageTableHelper.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/IStorageTableHelper.cs new file mode 100644 index 000000000..135a5284d --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/IStorageTableHelper.cs @@ -0,0 +1,20 @@ +using Microsoft.Azure.Cosmos.Table; +using System.Text.Json; + +namespace Cloud5mins.ShortenerTools.Core.Domain; + +public interface IStorageTableHelper +{ + CloudStorageAccount CreateStorageAccountFromConnectionString(); + Task GetShortUrlEntity(ShortUrlEntity row); + Task> GetAllShortUrlEntities(); + Task GetShortUrlEntityByVanity(string vanity); + Task SaveClickStatsEntity(ClickStatsEntity newStats); + Task SaveShortUrlEntity(ShortUrlEntity newShortUrl); + Task IfShortUrlEntityExistByVanity(string vanity); + Task IfShortUrlEntityExist(ShortUrlEntity row); + Task GetNextTableId(); + Task UpdateShortUrlEntity(ShortUrlEntity urlEntity); + Task> GetAllStatsByVanity(string vanity); + Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity); +} diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs index 164734223..05f08a7e4 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs @@ -3,7 +3,7 @@ namespace Cloud5mins.ShortenerTools.Core.Domain { - public class StorageTableHelper + public class StorageTableHelper: IStorageTableHelper { private string StorageConnectionString { get; set; } diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs index 3bbd7e1e0..0c3b319bf 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs @@ -20,7 +20,7 @@ public static class Utility //sets the length of the unique code to add to vanity private const int MinVanityCodeLength = 5; - public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) + public static async Task GetValidEndUrl(string vanity, IStorageTableHelper stgHelper) { if (string.IsNullOrEmpty(vanity)) { From 9e12b5b53da300426c0352a6a2caa5108ecce3f6 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 27 Jan 2025 17:10:22 -0500 Subject: [PATCH 05/41] UrlList migrated --- .../Cloud5mins.ShortenerTools.Api.http | 12 +++- .../ShortenerEnpoints.cs | 57 ++++++++++++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http index 87dffd4fd..fc1845805 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -1,13 +1,16 @@ ## @Cloud5mins.ShortenerTools.Api_HostAddress = http://localhost:5288 @Cloud5mins.ShortenerTools.Api_HostAddress = https://localhost:7187 +### #################################### +### weatherforecast GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/weatherforecast/ Accept: application/json +### #################################### ### Get a Welcome message Get {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api - +### #################################### ### Create a new short URL POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate @@ -44,4 +47,11 @@ Content-Type: application/json "Title": ".NET Aspire Azure Blob Storage integration" } +### #################################### +### Get list of URLs + +GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlList +Accept: application/json + + diff --git a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs index b362b95b5..2910d72b9 100644 --- a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs +++ b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs @@ -13,12 +13,16 @@ public static void MapShortenerEnpoints(this IEndpointRouteBuilder app) var endpoints = app.MapGroup("api") .WithOpenApi(); - endpoints.MapPost("/", GetWelcomeMessage) + endpoints.MapGet("/", GetWelcomeMessage) .WithDescription("Welcome to Cloud5mins URL Shortener API"); - endpoints.MapGet("/UrlCreate", UrlCreate) - .WithDescription("Create a new note") - .WithDisplayName("UrlCreate"); + endpoints.MapPost("/UrlCreate", UrlCreate) + .WithDescription("Create a new Short URL") + .WithDisplayName("Url Create"); + + endpoints.MapGet("/UrlList", UrlList) + .WithDescription("List all Urls") + .WithDisplayName("Url List"); } @@ -84,10 +88,7 @@ static private async Task, + InternalServerError + >> UrlList( IStorageTableHelper stgHelper, + HttpContext context, + ILogger logger) + { + logger.LogTrace("Starting UrlList..."); + + var result = new ListResponse(); + string userId = string.Empty; + + try + { + result.UrlList = await stgHelper.GetAllShortUrlEntities(); + result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); + + var host = GetHost(context); + + foreach (ShortUrlEntity url in result.UrlList) + { + url.ShortUrl = Utility.GetShortUrl(host, url.RowKey); + } + return TypedResults.Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "An unexpected error was encountered."); + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); + } + } + + private static string GetHost(HttpContext context) + { + string? customDomain = Environment.GetEnvironmentVariable("CustomDomain"); + var host = string.IsNullOrEmpty(customDomain) ? context.Request.Host.Value : customDomain; + return host ?? string.Empty; + } + } From a19ee569d0277f33b8fbce77d3230e74fb1355ec Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 27 Jan 2025 17:10:43 -0500 Subject: [PATCH 06/41] TinyBlazor Admin website added --- src/AzUrlShortener.sln | 6 + .../Cloud5mins.ShortenerTools.AppHost.csproj | 1 + .../Program.cs | 5 +- ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 17 ++ .../Components/App.razor | 22 ++ .../Components/Layout/MainLayout.razor | 34 ++++ .../Components/Layout/NavMenu.razor | 19 ++ .../Components/Pages/Counter.razor | 19 ++ .../Components/Pages/Error.razor | 36 ++++ .../Components/Pages/Home.razor | 7 + .../Components/Pages/Settings.razor | 47 +++++ .../Components/Routes.razor | 6 + .../Components/_Imports.razor | 12 ++ .../Program.cs | 32 +++ .../Properties/launchSettings.json | 38 ++++ .../appsettings.json | 9 + .../wwwroot/app.css | 188 ++++++++++++++++++ .../wwwroot/favicon.ico | Bin 0 -> 15086 bytes 18 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/App.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Error.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Settings.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Routes.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/_Imports.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/appsettings.json create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/app.css create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico diff --git a/src/AzUrlShortener.sln b/src/AzUrlShortener.sln index 81f3998bc..e908af05b 100644 --- a/src/AzUrlShortener.sln +++ b/src/AzUrlShortener.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.Api", "Cloud5mins.ShortenerTools.Api\Cloud5mins.ShortenerTools.Api.csproj", "{85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloud5mins.ShortenerTools.TinyBlazorAdmin", "Cloud5mins.ShortenerTools.TinyBlazorAdmin\Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj", "{88EAB6E9-CD20-4A6B-86A0-38CACA87FC7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {85C6D9A8-EB37-4FA3-9E83-7178825C9EA5}.Release|Any CPU.Build.0 = Release|Any CPU + {88EAB6E9-CD20-4A6B-86A0-38CACA87FC7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88EAB6E9-CD20-4A6B-86A0-38CACA87FC7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88EAB6E9-CD20-4A6B-86A0-38CACA87FC7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88EAB6E9-CD20-4A6B-86A0-38CACA87FC7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj index 3e6969a33..3575049d0 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj +++ b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs index f32531673..4c545fe1d 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs +++ b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs @@ -15,7 +15,10 @@ .WithEnvironment("data-storage-connstr",connectionString) .WithEnvironment("CustomDomain",customDomain) .WithEnvironment("DefaultRedirectUrl",defaultRedirectUrl) - .WithExternalHttpEndpoints(); + .WithExternalHttpEndpoints(); // only while debugging +builder.AddProject("admin") + .WithExternalHttpEndpoints() + .WithReference(manAPI); builder.Build().Run(); diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj new file mode 100644 index 000000000..0352757b0 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/App.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/App.razor new file mode 100644 index 000000000..4ed79ec11 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/App.razor @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor new file mode 100644 index 000000000..d0d41e901 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor @@ -0,0 +1,34 @@ +@inherits LayoutComponentBase + + + Cloud5mins.ShortenerTools.TinyBlazorAdmin + + + + +
+ @Body +
+
+
+ + + + +
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + +@code { + public DesignThemeModes Mode { get; set; } + public OfficeColor? OfficeColor { get; set; } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor new file mode 100644 index 000000000..85344ef4d --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor @@ -0,0 +1,19 @@ +@rendermode InteractiveServer + + + +@code { + private bool expanded = true; + +} + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor new file mode 100644 index 000000000..c7623ca7c --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode InteractiveServer + +Counter + +

Counter

+ +

Current count: @currentCount

+ +Click me + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Error.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Error.razor new file mode 100644 index 000000000..927fa988e --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + public HttpContext? HttpContext { get; set; } + + public string? RequestId { get; set; } + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor new file mode 100644 index 000000000..96714a213 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new Fluent Blazor app. \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Settings.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Settings.razor new file mode 100644 index 000000000..8f8d5cbed --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Settings.razor @@ -0,0 +1,47 @@ +@page "/settings" + +@using Microsoft.FluentUI.AspNetCore.Components.Extensions +@inject ILogger Logger + +@rendermode InteractiveServer + + + +

Settings

+ +
+ + + + + + + @context + + + + +
+ +@code { + public DesignThemeModes Mode { get; set; } + public OfficeColor? OfficeColor { get; set; } + + void OnLoaded(LoadedEventArgs e) + { + Logger.LogInformation($"Loaded: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}"); + } + + void OnLuminanceChanged(LuminanceChangedEventArgs e) + { + Logger.LogInformation($"Changed: {(e.Mode == DesignThemeModes.System ? "System" : "")} {(e.IsDark ? "Dark" : "Light")}"); + } + +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Routes.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Routes.razor new file mode 100644 index 000000000..d0df78161 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/_Imports.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/_Imports.razor new file mode 100644 index 000000000..50669122c --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.FluentUI.AspNetCore.Components +@using Microsoft.JSInterop +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components +@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs new file mode 100644 index 000000000..0e08f41e4 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs @@ -0,0 +1,32 @@ +using Microsoft.FluentUI.AspNetCore.Components; +using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); +builder.Services.AddFluentUIComponents(); + +var app = builder.Build(); +app.MapDefaultEndpoints(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json new file mode 100644 index 000000000..4a193c666 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:46575", + "sslPort": 44360 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5026", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7203;http://localhost:5026", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/appsettings.json b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/app.css b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/app.css new file mode 100644 index 000000000..b09a8f7f2 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/app.css @@ -0,0 +1,188 @@ +@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css'; + +body { + --body-font: "Segoe UI Variable", "Segoe UI", sans-serif; + font-family: var(--body-font); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + margin: 0; +} + +.navmenu-icon { + display: none; +} + +.main { + min-height: calc(100dvh - 86px); + color: var(--neutral-foreground-rest); + align-items: stretch !important; +} + +.body-content { + align-self: stretch; + height: unset !important; + display: flex; +} + +.content { + padding: 0.5rem 1.5rem; + align-self: stretch !important; +} + +.manage { + width: 100dvw; +} + +footer { + display: grid; + grid-template-columns: 10px auto auto 10px; + background: var(--neutral-layer-4); + color: var(--neutral-foreground-rest); + align-items: center; + padding: 10px 10px; +} + + footer .link1 { + grid-column: 2; + justify-content: start; + } + + footer .link2 { + grid-column: 3; + justify-self: end; + } + + footer a { + color: var(--neutral-foreground-rest); + text-decoration: none; + } + + footer a:focus { + outline: 1px dashed; + outline-offset: 3px; + } + + footer a:hover { + text-decoration: underline; + } + +.alert { + border: 1px dashed var(--accent-fill-rest); + padding: 5px; +} + + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; + margin: 20px 0; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::before { + content: "An error has occurred. " + } + +.loading-progress { + position: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } + +code { + color: #c02d76; +} + +@media (max-width: 600px) { + .main { + flex-direction: column !important; + row-gap: 0 !important; + } + + nav.sitenav { + width: 100%; + height: 100%; + } + + #main-menu { + width: 100% !important; + } + + #main-menu > div:first-child:is(.expander) { + display: none; + } + + .navmenu { + width: 100%; + } + + #navmenu-toggle { + appearance: none; + } + + #navmenu-toggle ~ nav { + display: none; + } + + #navmenu-toggle:checked ~ nav { + display: block; + } + + .navmenu-icon { + cursor: pointer; + z-index: 10; + display: block; + position: absolute; + top: 15px; + right: 20px; + width: 20px; + height: 20px; + border: none; + } +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e189d8e579e552ab2b3c82a949bccbc5ea00f3ed GIT binary patch literal 15086 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 literal 0 HcmV?d00001 From 015ea9222b28f5d7b1a812bfdfe379d51fa82e54 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 27 Jan 2025 18:16:17 -0500 Subject: [PATCH 07/41] work in progress --- ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 3 +- .../Components/Layout/NavMenu.razor | 1 + .../Components/Pages/UrlManager.razor | 305 ++++++++++++++++++ .../Program.cs | 7 + .../UrlManagerClient.cs | 20 ++ 5 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj index 0352757b0..4f31885ac 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -12,6 +12,7 @@ + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor index 85344ef4d..695b81ed8 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor @@ -7,6 +7,7 @@ Home Counter + urlmanager Settings diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor new file mode 100644 index 000000000..786343fef --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -0,0 +1,305 @@ +@page "/urlmanager" + +@using System.Net +@using Cloud5mins.ShortenerTools.Core.Domain +@using Cloud5mins.ShortenerTools.Core.Messages; +@inject UrlManagerClient urlManager +@inject IJSRuntime JSRuntime +@inject NavigationManager NavigationManager + + +Url Manager + +

Urls Manager

+

Create, Edit, Achives your URLs

+ +@if (urlList == null) +{ +

Loading...

+} +else +{ + + + + + + @* Add button here *@ + + + + + +   Nothing to see here. Carry on! + + + + @* + + + + + + + + + + + + + + + + + + + + + *@ +} + + +@if (ShowCreatePopup) +{ + @* *@ + +} + + +@if (ShowEditPopup) +{ + @* *@ + +} + +@code { + bool ShowCreatePopup = false; + bool ShowEditPopup = false; + private ListResponse urls = new ListResponse(); + + IQueryable urlList; + + ShortUrlRequest shortUrlRequest = new ShortUrlRequest(); + ShortUrlEntity editedUrl; + @* SfGrid grdUrls; *@ + public List gridData { get; set; } + + + protected override async Task OnInitializedAsync() + { + await RefreshGrid(); + } + + private async Task RefreshGrid() + { + try + { + var urlList = urlManager.GetUrls(); + @* using var response = await Http.GetAsync("/api/UrlList"); + if(response.IsSuccessStatusCode){ + urls = await response.Content.ReadFromJsonAsync(); + urlList = urls.UrlList.AsQueryable(); + } + else{ + switch (response.StatusCode) + { + case HttpStatusCode.Unauthorized: NavigationManager.NavigateTo("/unauthorized"); + break; + default: NavigationManager.NavigateTo("/404"); + break; + } + } *@ + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + private async Task UpdateUIList() + { + await RefreshGrid(); + StateHasChanged(); + } + private async Task SaveShortUrl() + { + @* ShowCreatePopup = false; + + try + { + await Http.PostAsJsonAsync("/api/UrlCreate", shortUrlRequest); + await UpdateUIList(); *@ + @* await grdUrls.ClearFilteringAsync(); + await grdUrls.FilterByColumnAsync("RowKey", "equal", shortUrlRequest.Vanity); *@ + @* } + catch (System.Exception ex) + { + Console.WriteLine(ex.ToString()); + } *@ + } + + void ClosePopup() + { + ShowCreatePopup = false; + ShowEditPopup = false; + } + + void CreateShortUrl() + { + shortUrlRequest = new ShortUrlRequest(); + ShowCreatePopup = true; + } + + [Inject] public IJSRuntime JsRuntime { get; set; } + public async Task CopyToClipboardAsync(string url) + { + await JSRuntime.InvokeVoidAsync("clipboardCopy.copyText", url); + } + + + void EditShortUrl(ShortUrlEntity urlEntity) + { + editedUrl = urlEntity; + ShowEditPopup = true; + } + + private async Task SaveUpdatedShortUrl() + { + @* ShowEditPopup = false; + await Http.PostAsJsonAsync("/api/UrlUpdate", editedUrl); + await UpdateUIList(); *@ + } + + private void NavigateToStats(string vanity){ + NavigationManager.NavigateTo("/Statistics/" + vanity); + } + + public async Task ArchiveShortUrl(ShortUrlEntity urlEntity) + { + @* await Http.PostAsJsonAsync("/api/UrlArchive", urlEntity); + await UpdateUIList(); *@ + } + +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs index 0e08f41e4..91df8d79d 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs @@ -1,10 +1,16 @@ using Microsoft.FluentUI.AspNetCore.Components; using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components; +using Cloud5mins.ShortenerTools.TinyBlazorAdmin; var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.AddHttpClient(client => + { + client.BaseAddress = new Uri("https+http://api"); + }); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -13,6 +19,7 @@ var app = builder.Build(); app.MapDefaultEndpoints(); + // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs new file mode 100644 index 000000000..9bef34fe0 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs @@ -0,0 +1,20 @@ +using System; +using Cloud5mins.ShortenerTools.Core.Domain; +using Cloud5mins.ShortenerTools.Core.Messages; + +namespace Cloud5mins.ShortenerTools.TinyBlazorAdmin; + +public class UrlManagerClient(HttpClient httpClient) +{ + + public async Task?> GetUrls() + { + IQueryable urlList = null; + using var response = await httpClient.GetAsync("/api/UrlList"); + if(response.IsSuccessStatusCode){ + var urls = await response.Content.ReadFromJsonAsync(); + urlList = urls!.UrlList.AsQueryable(); + } + return urlList; + } +} From a24247cb0a57dd143fb242d03ad68385f886d062 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Jan 2025 16:11:27 -0500 Subject: [PATCH 08/41] List URl completed --- .../Components/Layout/NavMenu.razor | 2 +- .../Components/Pages/UrlManager.razor | 121 +++++------------- .../UrlManagerClient.cs | 14 +- 3 files changed, 44 insertions(+), 93 deletions(-) diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor index 695b81ed8..271dd0d74 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor @@ -7,7 +7,7 @@ Home Counter - urlmanager + URL Manager Settings diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor index 786343fef..5d7214bbf 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -7,91 +7,48 @@ @inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager +@rendermode InteractiveServer Url Manager

Urls Manager

Create, Edit, Achives your URLs

-@if (urlList == null) -{ -

Loading...

-} -else -{ - +Add New Url - +
+ - - @* Add button here *@ + + + + + @(context!.Schedules?.Count ?? 0) + + + @context!.RowKey + + @context.Clicks + + + + + + -   Nothing to see here. Carry on! +   Nothing to see here. Carry on! - - @* - - - - - - - - - - - - - - - - - - - - - *@ -} +
+ @if (ShowCreatePopup) @@ -199,13 +156,15 @@ else bool ShowEditPopup = false; private ListResponse urls = new ListResponse(); - IQueryable urlList; - + IQueryable? urlList; + PaginationState pagination = new PaginationState { ItemsPerPage = 20 }; ShortUrlRequest shortUrlRequest = new ShortUrlRequest(); ShortUrlEntity editedUrl; - @* SfGrid grdUrls; *@ public List gridData { get; set; } + GridSort sortByClicks = GridSort.ByDescending(p => p.Clicks); + GridSort sortBySchedules = GridSort.ByDescending(p => p.Schedules.Count); + GridSort sortByVanities = GridSort.ByAscending(p => p.RowKey); protected override async Task OnInitializedAsync() { @@ -216,21 +175,7 @@ else { try { - var urlList = urlManager.GetUrls(); - @* using var response = await Http.GetAsync("/api/UrlList"); - if(response.IsSuccessStatusCode){ - urls = await response.Content.ReadFromJsonAsync(); - urlList = urls.UrlList.AsQueryable(); - } - else{ - switch (response.StatusCode) - { - case HttpStatusCode.Unauthorized: NavigationManager.NavigateTo("/unauthorized"); - break; - default: NavigationManager.NavigateTo("/404"); - break; - } - } *@ + urlList = await urlManager.GetUrls(); } catch (Exception ex) { diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs index 9bef34fe0..792259c8f 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs @@ -10,11 +10,17 @@ public class UrlManagerClient(HttpClient httpClient) public async Task?> GetUrls() { IQueryable urlList = null; - using var response = await httpClient.GetAsync("/api/UrlList"); - if(response.IsSuccessStatusCode){ - var urls = await response.Content.ReadFromJsonAsync(); - urlList = urls!.UrlList.AsQueryable(); + try{ + using var response = await httpClient.GetAsync("/api/UrlList"); + if(response.IsSuccessStatusCode){ + var urls = await response.Content.ReadFromJsonAsync(); + urlList = urls!.UrlList.AsQueryable(); + } } + catch(Exception ex){ + Console.WriteLine(ex.Message); + } + return urlList; } } From 4929f228a10e7b56165dd72d5445d9dae1819e58 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 4 Feb 2025 16:19:31 -0500 Subject: [PATCH 09/41] Create new url works --- .../Messages/ShortUrlRequest.cs | 12 ++- .../Components/Dialogues/NewUrlDialog.razor | 84 +++++++++++++++++++ .../Components/Layout/MainLayout.razor | 5 +- .../Components/Pages/UrlManager.razor | 43 +++++++--- .../UrlManagerClient.cs | 15 ++++ 5 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor diff --git a/src/Cloud5mins.ShortenerTools.Core/Messages/ShortUrlRequest.cs b/src/Cloud5mins.ShortenerTools.Core/Messages/ShortUrlRequest.cs index 64ed76762..3e7a9844b 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Messages/ShortUrlRequest.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Messages/ShortUrlRequest.cs @@ -1,4 +1,5 @@ using Cloud5mins.ShortenerTools.Core.Domain; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Cloud5mins.ShortenerTools.Core.Messages @@ -7,7 +8,7 @@ public class ShortUrlRequest { private string _vanity; - public string Title { get; set; } + public string? Title { get; set; } public string Vanity { @@ -41,5 +42,14 @@ public List Schedules _schedules = value; } } + + public bool Validate() + { + if (string.IsNullOrEmpty(Url)) + { + return false; + } + return true; + } } } \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor new file mode 100644 index 000000000..8ab04d5e4 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor @@ -0,0 +1,84 @@ +@using Cloud5mins.ShortenerTools.Core.Messages +@implements IDialogContentComponent + +@rendermode InteractiveServer + + + + + + @Dialog!.Instance.Parameters.Title + + + + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ @* *@ +
+ +
+ +
+ + + + + Save + Cancel + + + +@code { + [Parameter] + public ShortUrlRequest Content { get; set; } = default!; + + [CascadingParameter] + public FluentDialog? Dialog { get; set; } = default!; + + private ShortUrlRequest _shortUrl = new ShortUrlRequest(); + + + protected override void OnInitialized() + { + _shortUrl = Content; + } + private void ToggleDialogPrimaryActionButton(bool enable) + { + Dialog!.TogglePrimaryActionButton(enable); + } + + private async Task SaveAsync() + { + if (_shortUrl.Validate()) + { + await Dialog!.CloseAsync(_shortUrl); + } + } + + private async Task CancelAsync() + { + await Dialog!.CancelAsync(); + } +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor index d0d41e901..97f14b6b9 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor @@ -1,7 +1,8 @@ @inherits LayoutComponentBase + - Cloud5mins.ShortenerTools.TinyBlazorAdmin + Tiny Blazor Admin @@ -21,6 +22,8 @@ + +
An unhandled error has occurred. Reload diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor index 5d7214bbf..3d983798c 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -3,8 +3,10 @@ @using System.Net @using Cloud5mins.ShortenerTools.Core.Domain @using Cloud5mins.ShortenerTools.Core.Messages; +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components.Dialogues @inject UrlManagerClient urlManager @inject IJSRuntime JSRuntime +@inject IDialogService DialogService @inject NavigationManager NavigationManager @rendermode InteractiveServer @@ -16,12 +18,14 @@ Add New Url -
+
+ Pagination="@pagination" + RowSize="DataGridRowSize.Medium" + ShowHover=true> @@ -69,21 +73,18 @@
- +
- +
- +
@@ -158,7 +159,7 @@ IQueryable? urlList; PaginationState pagination = new PaginationState { ItemsPerPage = 20 }; - ShortUrlRequest shortUrlRequest = new ShortUrlRequest(); + ShortUrlRequest shortUrlRequest; ShortUrlEntity editedUrl; public List gridData { get; set; } @@ -211,10 +212,30 @@ ShowEditPopup = false; } - void CreateShortUrl() + private async Task CreateShortUrl() { shortUrlRequest = new ShortUrlRequest(); - ShowCreatePopup = true; + //ShowCreatePopup = true; + var dialog = await DialogService.ShowDialogAsync(shortUrlRequest, new DialogParameters() + { + Title = "Create a new Short Url", + PreventDismissOnOverlayClick = true, + PreventScroll = true, + }); + + var result = await dialog.Result; + if (!result.Cancelled && result.Data != null) + { + var dialogData = (ShortUrlRequest)result.Data; + var urlRequest = new ShortRequest + { + Title = dialogData.Title ?? string.Empty, + Url = dialogData.Url, + Vanity = dialogData.Vanity, + Schedules = dialogData.Schedules.ToArray() + }; + var response = await urlManager.UrlCreate(urlRequest); + } } [Inject] public IJSRuntime JsRuntime { get; set; } diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs index 792259c8f..f95a2003b 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs @@ -23,4 +23,19 @@ public class UrlManagerClient(HttpClient httpClient) return urlList; } + + public async Task UrlCreate(ShortRequest url) + { + try{ + using var response = await httpClient.PostAsJsonAsync("/api/UrlCreate", url); + if(response.IsSuccessStatusCode){ + return true; + } + } + catch(Exception ex){ + Console.WriteLine(ex.Message); + } + + return false; + } } From dfd54bc45ea0440ff65cef60416bc2021aee9747 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 Feb 2025 17:21:02 -0500 Subject: [PATCH 10/41] new sdk mvp works. --- .../Cloud5mins.ShortenerTools.Api.csproj | 1 + .../Cloud5mins.ShortenerTools.Api.http | 16 +- src/Cloud5mins.ShortenerTools.Api/Program.cs | 6 +- .../ShortenerEnpoints.cs | 73 ++++++--- .../Cloud5mins.ShortenerTools.AppHost.csproj | 6 +- .../Program.cs | 26 +++- .../Cloud5mins.ShortenerTools.Core.csproj | 1 + .../Domain/ShortUrlEntity2.cs | 140 ++++++++++++++++++ .../Service/AzStrorageTablesService.cs | 57 +++++++ .../Service/IAzStrorageTablesService.cs | 12 ++ 10 files changed, 295 insertions(+), 43 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj index 035095b98..da61569df 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http index fc1845805..9b3d4b5ff 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -1,19 +1,19 @@ -## @Cloud5mins.ShortenerTools.Api_HostAddress = http://localhost:5288 -@Cloud5mins.ShortenerTools.Api_HostAddress = https://localhost:7187 +## @Api_HostAddress = http://localhost:5288 +@Api_HostAddress = https://localhost:7187 ### #################################### ### weatherforecast -GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/weatherforecast/ +GET {{Api_HostAddress}}/weatherforecast/ Accept: application/json ### #################################### ### Get a Welcome message -Get {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api +Get {{Api_HostAddress}}/api ### #################################### ### Create a new short URL -POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json Content-Type: application/json @@ -25,7 +25,7 @@ Content-Type: application/json ### ERROR no URL -POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json Content-Type: application/json @@ -37,7 +37,7 @@ Content-Type: application/json ### ERROR URL no valide -POST {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlCreate +POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json Content-Type: application/json @@ -50,7 +50,7 @@ Content-Type: application/json ### #################################### ### Get list of URLs -GET {{Cloud5mins.ShortenerTools.Api_HostAddress}}/api/UrlList +GET {{Api_HostAddress}}/api/UrlList Accept: application/json diff --git a/src/Cloud5mins.ShortenerTools.Api/Program.cs b/src/Cloud5mins.ShortenerTools.Api/Program.cs index e4658b360..82381ac26 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Program.cs +++ b/src/Cloud5mins.ShortenerTools.Api/Program.cs @@ -1,4 +1,5 @@ using Cloud5mins.ShortenerTools.Core.Domain; +using Cloud5mins.ShortenerTools.Core.Service; var builder = WebApplication.CreateBuilder(args); @@ -8,9 +9,8 @@ // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); -string connStr = Environment.GetEnvironmentVariable("data-storage-connstr")?? string.Empty; +builder.AddAzureTableClient("strTables"); -builder.Services.AddTransient(sp => new StorageTableHelper(connStr)); builder.Services.AddTransient(sp => { var loggerFactory = sp.GetRequiredService(); @@ -30,6 +30,8 @@ app.MapOpenApi(); } + + app.UseHttpsRedirection(); app.MapShortenerEnpoints(); diff --git a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs index 2910d72b9..3a969c653 100644 --- a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs +++ b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs @@ -1,9 +1,11 @@ using System.Data; using System.Threading.Tasks.Dataflow; using System.Xml.Linq; +using Azure.Data.Tables; using Cloud5mins.ShortenerTools; using Cloud5mins.ShortenerTools.Core.Domain; using Cloud5mins.ShortenerTools.Core.Messages; +using Cloud5mins.ShortenerTools.Core.Service; using Microsoft.AspNetCore.Http.HttpResults; public static class ShortenerEnpoints @@ -37,14 +39,15 @@ static private async Task, Conflict, InternalServerError - >> UrlCreate(ShortRequest request, - IStorageTableHelper stgHelper, + >> UrlCreate(ShortRequest request, + TableServiceClient tblClient, HttpContext context, ILogger logger) { logger.LogTrace($"creating shortURL: {request.Url}"); var result = new ShortResponse(); + IAzStrorageTablesService stgHelper = new AzStrorageTablesService(tblClient); try { @@ -70,26 +73,28 @@ static private async Task(new DetailedBadRequest { Message = ErrorMsg }); - } - } - else - { - newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); - } - - await stgHelper.SaveShortUrlEntity(newRow); + // if (!string.IsNullOrEmpty(vanity)) + // { + // newRow = new ShortUrlEntity(longUrl, vanity, title, request.Schedules); + // if (await stgHelper.IfShortUrlEntityExist(newRow)) + // { + // // throw new Exception("This Short URL already exist."); + // string ErrorMsg = "This Short URL already exist."; + // logger.LogInformation(ErrorMsg); + // return TypedResults.Conflict(new DetailedBadRequest { Message = ErrorMsg }); + // } + // } + // else + // { + // newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); + // } + + ShortUrlEntity2 newRow2 = new ShortUrlEntity2(longUrl, vanity, title, request.Schedules); + + await stgHelper.SaveShortUrlEntity(newRow2); var host = GetHost(context); - result = new ShortResponse(host!, newRow.Url, newRow.RowKey, newRow.Title); + result = new ShortResponse(host!, newRow2.Url, newRow2.RowKey, newRow2.Title); logger.LogTrace("Short Url created."); @@ -107,19 +112,39 @@ static private async Task, InternalServerError - >> UrlList( IStorageTableHelper stgHelper, - HttpContext context, + >> UrlList( //IAzStrorageTablesService stgHelper, + TableServiceClient tblClient, + HttpContext context, ILogger logger) { logger.LogTrace("Starting UrlList..."); var result = new ListResponse(); string userId = string.Empty; + IAzStrorageTablesService stgHelper = new AzStrorageTablesService(tblClient); try { - result.UrlList = await stgHelper.GetAllShortUrlEntities(); - result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); + // result.UrlList = await stgHelper.GetAllShortUrlEntities(); + // result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); + + + List allUrls = await stgHelper.GetAllShortUrlEntities(); + var filtredUlrs = allUrls.Where(p => !(p.IsArchived ?? false)).ToList(); + + // Insert into result.UrlList all filtredUlrs mapping all properties + result.UrlList = filtredUlrs.Select(p => new ShortUrlEntity + { + PartitionKey = p.PartitionKey, + RowKey = p.RowKey, + Url = p.Url, + Title = p.Title, + Clicks = p.Clicks, + IsArchived = p.IsArchived, + Schedules = p.Schedules + }).ToList(); + + var host = GetHost(context); diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj index 3575049d0..6e3497b70 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj +++ b/src/Cloud5mins.ShortenerTools.AppHost/Cloud5mins.ShortenerTools.AppHost.csproj @@ -1,8 +1,10 @@ + + Exe - net8.0 + net9.0 enable enable true @@ -10,7 +12,7 @@ - + diff --git a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs index 4c545fe1d..3a6d5070b 100644 --- a/src/Cloud5mins.ShortenerTools.AppHost/Program.cs +++ b/src/Cloud5mins.ShortenerTools.AppHost/Program.cs @@ -1,24 +1,36 @@ +using Microsoft.Extensions.Hosting; + var builder = DistributedApplication.CreateBuilder(args); -var connectionString = builder.AddConnectionString("data-storage-connstr"); +//var connectionString = builder.AddConnectionString("data-storage-connstr"); var customDomain = builder.AddParameter("CustomDomain"); var defaultRedirectUrl = builder.AddParameter("DefaultRedirectUrl"); -//var urlData = builder.AddAzureStorage("url-data"); +var urlStorage = builder.AddAzureStorage("url-data"); + +if (builder.Environment.IsDevelopment()) +{ + urlStorage.RunAsEmulator(); +} + +var strTables = urlStorage.AddTables("strTables"); var azFuncLight = builder.AddAzureFunctionsProject("azfunc-light") - //.WithHostStorage(urlData) + .WithReference(strTables) + .WaitFor(strTables) .WithExternalHttpEndpoints(); var manAPI = builder.AddProject("api") - .WithEnvironment("data-storage-connstr",connectionString) + //.WithEnvironment("data-storage-connstr",connectionString) + .WithReference(strTables) + .WaitFor(strTables) .WithEnvironment("CustomDomain",customDomain) .WithEnvironment("DefaultRedirectUrl",defaultRedirectUrl) .WithExternalHttpEndpoints(); // only while debugging -builder.AddProject("admin") - .WithExternalHttpEndpoints() - .WithReference(manAPI); +// builder.AddProject("admin") +// .WithExternalHttpEndpoints() +// .WithReference(manAPI); builder.Build().Run(); diff --git a/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj b/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj index 4181074b4..157ea2f81 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj +++ b/src/Cloud5mins.ShortenerTools.Core/Cloud5mins.ShortenerTools.Core.csproj @@ -5,6 +5,7 @@ enable + diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs new file mode 100644 index 000000000..a1759b42e --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text.Json; +using Azure; +using Azure.Data.Tables; + +namespace Cloud5mins.ShortenerTools.Core.Domain +{ + public class ShortUrlEntity2: ITableEntity + { + public string Url { get; set; } + private string _activeUrl { get; set; } + + public string ActiveUrl + { + get + { + if (String.IsNullOrEmpty(_activeUrl)) + _activeUrl = GetActiveUrl(); + return _activeUrl; + } + } + + + public string Title { get; set; } + + public string ShortUrl { get; set; } + + public int Clicks { get; set; } + + public bool? IsArchived { get; set; } + public string SchedulesPropertyRaw { get; set; } + + private List _schedules { get; set; } + + //[IgnoreProperty] + [IgnoreDataMember] + public List Schedules + { + get + { + if (_schedules == null) + { + if (String.IsNullOrEmpty(SchedulesPropertyRaw)) + { + _schedules = new List(); + } + else + { + _schedules = JsonSerializer.Deserialize(SchedulesPropertyRaw).ToList(); + } + } + return _schedules; + } + set + { + _schedules = value; + } + } + + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + + + public ShortUrlEntity2() { } + + public ShortUrlEntity2(string longUrl, string endUrl) + { + Initialize(longUrl, endUrl, string.Empty, null); + } + + public ShortUrlEntity2(string longUrl, string endUrl, Schedule[] schedules) + { + Initialize(longUrl, endUrl, string.Empty, schedules); + } + + public ShortUrlEntity2(string longUrl, string endUrl, string title, Schedule[] schedules) + { + Initialize(longUrl, endUrl, title, schedules); + } + + private void Initialize(string longUrl, string endUrl, string title, Schedule[] schedules) + { + PartitionKey = endUrl.First().ToString(); + RowKey = endUrl; + Url = longUrl; + Title = title; + Clicks = 0; + IsArchived = false; + + if(schedules?.Length>0) + { + Schedules = schedules.ToList(); + SchedulesPropertyRaw = JsonSerializer.Serialize>(Schedules); + } + } + + public static ShortUrlEntity GetEntity(string longUrl, string endUrl, string title, Schedule[] schedules) + { + return new ShortUrlEntity + { + PartitionKey = endUrl.First().ToString(), + RowKey = endUrl, + Url = longUrl, + Title = title, + Schedules = schedules.ToList() + }; + } + + private string GetActiveUrl() + { + if (Schedules != null) + return GetActiveUrl(DateTime.UtcNow); + return Url; + } + private string GetActiveUrl(DateTime pointInTime) + { + var link = Url; + var active = Schedules.Where(s => + s.End > pointInTime && //hasn't ended + s.Start < pointInTime //already started + ).OrderBy(s => s.Start); //order by start to process first link + + foreach (var sched in active.ToArray()) + { + if (sched.IsActive(pointInTime)) + { + link = sched.AlternativeUrl; + break; + } + } + return link; + } + } + +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs new file mode 100644 index 000000000..7efe3fa53 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs @@ -0,0 +1,57 @@ +using System; +using Azure.Data.Tables; +using Cloud5mins.ShortenerTools.Core.Domain; + +namespace Cloud5mins.ShortenerTools.Core.Service; + +public class AzStrorageTablesService(TableServiceClient client): IAzStrorageTablesService +{ + + private TableClient GetUrlsTable() + { + client.CreateTableIfNotExists("UrlsDetails"); + TableClient table = client.GetTableClient("UrlsDetails"); + return table; + } + + public async Task> GetAllShortUrlEntities() + { + TableClient tblUrls = GetUrlsTable(); + var lstShortUrl = new List(); + + // Retreiving all entities that are NOT the NextId entity + // (it's the only one in the partion "KEY") + var queryResult = tblUrls.QueryAsync(e => e.RowKey != "KEY"); + + await foreach (var emp in queryResult.AsPages()) + { + foreach (var item in emp.Values) + { + lstShortUrl.Add(item); + } + } + + return lstShortUrl; + } + + + public async Task SaveShortUrlEntity(ShortUrlEntity2 newShortUrl) + { + + // serializing the collection easier on json shares + //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); + + TableClient tblUrls = GetUrlsTable(); + var response = await tblUrls.AddEntityAsync(newShortUrl); + + var temp = response.Content; + return newShortUrl; + + + // TableOperation insOperation = TableOanything peration.InsertOrMerge(newShortUrl); + // TableResult result = await GetUrlsTable().ExecuteAsync(insOperation); + // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; + // return eShortUrl; + } + +} diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs b/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs new file mode 100644 index 000000000..790667926 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Cloud5mins.ShortenerTools.Core.Domain; + +namespace Cloud5mins.ShortenerTools.Core.Service; + +public interface IAzStrorageTablesService +{ + Task> GetAllShortUrlEntities(); + Task SaveShortUrlEntity(ShortUrlEntity2 newRow2); + +} From a7399a2a0ad1948d37a56d1ae8af0600ae29d658 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 Feb 2025 17:05:23 -0500 Subject: [PATCH 11/41] converting all storageHelper to AzStorageServices --- .../ShortenerEnpoints.cs | 48 +-- .../Program.cs | 6 +- .../Domain/ClickStatsEntity.cs | 33 +- .../Domain/NextId.cs | 18 +- .../Domain/ShortUrlEntity.cs | 15 +- .../Domain/ShortUrlEntity2.cs | 140 ------- .../Domain/ShortUrlEntity_old.cs | 131 +++++++ .../Domain/StorageTableHelper.cs | 344 +++++++++--------- .../Service/AzStrorageTablesService.cs | 156 +++++++- .../Service/IAzStrorageTablesService.cs | 14 +- .../Service/UrlServices.cs | 26 +- .../Service/Utility.cs | 3 +- .../Functions/UrlRedirect.cs | 8 +- .../Program.cs | 2 + 14 files changed, 536 insertions(+), 408 deletions(-) delete mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs create mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity_old.cs diff --git a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs index 3a969c653..600713b29 100644 --- a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs +++ b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs @@ -73,28 +73,26 @@ static private async Task(new DetailedBadRequest { Message = ErrorMsg }); - // } - // } - // else - // { - // newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); - // } - - ShortUrlEntity2 newRow2 = new ShortUrlEntity2(longUrl, vanity, title, request.Schedules); - - await stgHelper.SaveShortUrlEntity(newRow2); + if (!string.IsNullOrEmpty(vanity)) + { + newRow = new ShortUrlEntity(longUrl, vanity, title, request.Schedules); + if (await stgHelper.IfShortUrlEntityExist(newRow)) + { + // throw new Exception("This Short URL already exist."); + string ErrorMsg = "This Short URL already exist."; + logger.LogInformation(ErrorMsg); + return TypedResults.Conflict(new DetailedBadRequest { Message = ErrorMsg }); + } + } + else + { + newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); + } + + await stgHelper.SaveShortUrlEntity(newRow); var host = GetHost(context); - result = new ShortResponse(host!, newRow2.Url, newRow2.RowKey, newRow2.Title); + result = new ShortResponse(host!, newRow.Url, newRow.RowKey, newRow.Title); logger.LogTrace("Short Url created."); @@ -112,8 +110,7 @@ static private async Task, InternalServerError - >> UrlList( //IAzStrorageTablesService stgHelper, - TableServiceClient tblClient, + >> UrlList( TableServiceClient tblClient, HttpContext context, ILogger logger) { @@ -125,11 +122,7 @@ static private async Task !(p.IsArchived ?? false)).ToList(); - - - List allUrls = await stgHelper.GetAllShortUrlEntities(); + List allUrls = await stgHelper.GetAllShortUrlEntities(); var filtredUlrs = allUrls.Where(p => !(p.IsArchived ?? false)).ToList(); // Insert into result.UrlList all filtredUlrs mapping all properties @@ -145,7 +138,6 @@ static private async Task("admin") -// .WithExternalHttpEndpoints() -// .WithReference(manAPI); +builder.AddProject("admin") + .WithExternalHttpEndpoints() + .WithReference(manAPI); builder.Build().Run(); diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ClickStatsEntity.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ClickStatsEntity.cs index 86e67c32b..ca95a9a2a 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/ClickStatsEntity.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/ClickStatsEntity.cs @@ -1,22 +1,23 @@ -using Microsoft.Azure.Cosmos.Table; using System; +using Azure; +using Azure.Data.Tables; -namespace Cloud5mins.ShortenerTools.Core.Domain +namespace Cloud5mins.ShortenerTools.Core.Domain; + +public class ClickStatsEntity : ITableEntity { - public class ClickStatsEntity : TableEntity - { - //public string Id { get; set; } - public string Datetime { get; set; } + public string Datetime { get; set; } + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } - public ClickStatsEntity() { } + public ClickStatsEntity() { } - public ClickStatsEntity(string vanity) - { - PartitionKey = vanity; - RowKey = Guid.NewGuid().ToString(); - Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm"); - } + public ClickStatsEntity(string vanity) + { + PartitionKey = vanity; + RowKey = Guid.NewGuid().ToString(); + Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm"); } - - -} \ No newline at end of file +} diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/NextId.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/NextId.cs index 31b1ea053..8edc0f5fc 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/NextId.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/NextId.cs @@ -1,9 +1,13 @@ -using Microsoft.Azure.Cosmos.Table; +using Azure; +using Azure.Data.Tables; -namespace Cloud5mins.ShortenerTools.Core.Domain +namespace Cloud5mins.ShortenerTools.Core.Domain; + +public class NextId : ITableEntity { - public class NextId : TableEntity - { - public int Id { get; set; } - } -} \ No newline at end of file + public int Id { get; set; } + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } +} diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs index 5f7f63ade..d9a367c0b 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs @@ -1,12 +1,14 @@ -using Microsoft.Azure.Cosmos.Table; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Text.Json; +using Azure; +using Azure.Data.Tables; namespace Cloud5mins.ShortenerTools.Core.Domain { - public class ShortUrlEntity : TableEntity + public class ShortUrlEntity: ITableEntity { public string Url { get; set; } private string _activeUrl { get; set; } @@ -33,7 +35,8 @@ public string ActiveUrl private List _schedules { get; set; } - [IgnoreProperty] + //[IgnoreProperty] + [IgnoreDataMember] public List Schedules { get @@ -57,6 +60,12 @@ public List Schedules } } + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + + public ShortUrlEntity() { } public ShortUrlEntity(string longUrl, string endUrl) diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs deleted file mode 100644 index a1759b42e..000000000 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity2.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text.Json; -using Azure; -using Azure.Data.Tables; - -namespace Cloud5mins.ShortenerTools.Core.Domain -{ - public class ShortUrlEntity2: ITableEntity - { - public string Url { get; set; } - private string _activeUrl { get; set; } - - public string ActiveUrl - { - get - { - if (String.IsNullOrEmpty(_activeUrl)) - _activeUrl = GetActiveUrl(); - return _activeUrl; - } - } - - - public string Title { get; set; } - - public string ShortUrl { get; set; } - - public int Clicks { get; set; } - - public bool? IsArchived { get; set; } - public string SchedulesPropertyRaw { get; set; } - - private List _schedules { get; set; } - - //[IgnoreProperty] - [IgnoreDataMember] - public List Schedules - { - get - { - if (_schedules == null) - { - if (String.IsNullOrEmpty(SchedulesPropertyRaw)) - { - _schedules = new List(); - } - else - { - _schedules = JsonSerializer.Deserialize(SchedulesPropertyRaw).ToList(); - } - } - return _schedules; - } - set - { - _schedules = value; - } - } - - public string PartitionKey { get; set; } - public string RowKey { get; set; } - public DateTimeOffset? Timestamp { get; set; } - public ETag ETag { get; set; } - - - public ShortUrlEntity2() { } - - public ShortUrlEntity2(string longUrl, string endUrl) - { - Initialize(longUrl, endUrl, string.Empty, null); - } - - public ShortUrlEntity2(string longUrl, string endUrl, Schedule[] schedules) - { - Initialize(longUrl, endUrl, string.Empty, schedules); - } - - public ShortUrlEntity2(string longUrl, string endUrl, string title, Schedule[] schedules) - { - Initialize(longUrl, endUrl, title, schedules); - } - - private void Initialize(string longUrl, string endUrl, string title, Schedule[] schedules) - { - PartitionKey = endUrl.First().ToString(); - RowKey = endUrl; - Url = longUrl; - Title = title; - Clicks = 0; - IsArchived = false; - - if(schedules?.Length>0) - { - Schedules = schedules.ToList(); - SchedulesPropertyRaw = JsonSerializer.Serialize>(Schedules); - } - } - - public static ShortUrlEntity GetEntity(string longUrl, string endUrl, string title, Schedule[] schedules) - { - return new ShortUrlEntity - { - PartitionKey = endUrl.First().ToString(), - RowKey = endUrl, - Url = longUrl, - Title = title, - Schedules = schedules.ToList() - }; - } - - private string GetActiveUrl() - { - if (Schedules != null) - return GetActiveUrl(DateTime.UtcNow); - return Url; - } - private string GetActiveUrl(DateTime pointInTime) - { - var link = Url; - var active = Schedules.Where(s => - s.End > pointInTime && //hasn't ended - s.Start < pointInTime //already started - ).OrderBy(s => s.Start); //order by start to process first link - - foreach (var sched in active.ToArray()) - { - if (sched.IsActive(pointInTime)) - { - link = sched.AlternativeUrl; - break; - } - } - return link; - } - } - -} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity_old.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity_old.cs new file mode 100644 index 000000000..58d89fcdc --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity_old.cs @@ -0,0 +1,131 @@ +// using Microsoft.Azure.Cosmos.Table; +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using System.Text.Json; + +// namespace Cloud5mins.ShortenerTools.Core.Domain +// { +// public class ShortUrlEntity_old : TableEntity +// { +// public string Url { get; set; } +// private string _activeUrl { get; set; } + +// public string ActiveUrl +// { +// get +// { +// if (String.IsNullOrEmpty(_activeUrl)) +// _activeUrl = GetActiveUrl(); +// return _activeUrl; +// } +// } + + +// public string Title { get; set; } + +// public string ShortUrl { get; set; } + +// public int Clicks { get; set; } + +// public bool? IsArchived { get; set; } +// public string SchedulesPropertyRaw { get; set; } + +// private List _schedules { get; set; } + +// [IgnoreProperty] +// public List Schedules +// { +// get +// { +// if (_schedules == null) +// { +// if (String.IsNullOrEmpty(SchedulesPropertyRaw)) +// { +// _schedules = new List(); +// } +// else +// { +// _schedules = JsonSerializer.Deserialize(SchedulesPropertyRaw).ToList(); +// } +// } +// return _schedules; +// } +// set +// { +// _schedules = value; +// } +// } + +// public ShortUrlEntity() { } + +// public ShortUrlEntity(string longUrl, string endUrl) +// { +// Initialize(longUrl, endUrl, string.Empty, null); +// } + +// public ShortUrlEntity(string longUrl, string endUrl, Schedule[] schedules) +// { +// Initialize(longUrl, endUrl, string.Empty, schedules); +// } + +// public ShortUrlEntity(string longUrl, string endUrl, string title, Schedule[] schedules) +// { +// Initialize(longUrl, endUrl, title, schedules); +// } + +// private void Initialize(string longUrl, string endUrl, string title, Schedule[] schedules) +// { +// PartitionKey = endUrl.First().ToString(); +// RowKey = endUrl; +// Url = longUrl; +// Title = title; +// Clicks = 0; +// IsArchived = false; + +// if(schedules?.Length>0) +// { +// Schedules = schedules.ToList(); +// SchedulesPropertyRaw = JsonSerializer.Serialize>(Schedules); +// } +// } + +// public static ShortUrlEntity GetEntity(string longUrl, string endUrl, string title, Schedule[] schedules) +// { +// return new ShortUrlEntity +// { +// PartitionKey = endUrl.First().ToString(), +// RowKey = endUrl, +// Url = longUrl, +// Title = title, +// Schedules = schedules.ToList() +// }; +// } + +// private string GetActiveUrl() +// { +// if (Schedules != null) +// return GetActiveUrl(DateTime.UtcNow); +// return Url; +// } +// private string GetActiveUrl(DateTime pointInTime) +// { +// var link = Url; +// var active = Schedules.Where(s => +// s.End > pointInTime && //hasn't ended +// s.Start < pointInTime //already started +// ).OrderBy(s => s.Start); //order by start to process first link + +// foreach (var sched in active.ToArray()) +// { +// if (sched.IsActive(pointInTime)) +// { +// link = sched.AlternativeUrl; +// break; +// } +// } +// return link; +// } +// } + +// } \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs index 05f08a7e4..de20b5d83 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs @@ -3,189 +3,189 @@ namespace Cloud5mins.ShortenerTools.Core.Domain { - public class StorageTableHelper: IStorageTableHelper + public class StorageTableHelper//: IStorageTableHelper { private string StorageConnectionString { get; set; } public StorageTableHelper() { } - public StorageTableHelper(string storageConnectionString) - { - StorageConnectionString = storageConnectionString; - } - public CloudStorageAccount CreateStorageAccountFromConnectionString() - { - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageConnectionString); - return storageAccount; - } - - private CloudTable GetTable(string tableName) - { - CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(); - CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration()); - CloudTable table = tableClient.GetTableReference(tableName); - table.CreateIfNotExists(); - - return table; - } - private CloudTable GetUrlsTable() - { - CloudTable table = GetTable("UrlsDetails"); - return table; - } - - private CloudTable GetStatsTable() - { - CloudTable table = GetTable("ClickStats"); - return table; - } - - public async Task GetShortUrlEntity(ShortUrlEntity row) - { - TableOperation selOperation = TableOperation.Retrieve(row.PartitionKey, row.RowKey); - TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); - ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; - return eShortUrl; - } - - public async Task> GetAllShortUrlEntities() - { - var tblUrls = GetUrlsTable(); - TableContinuationToken token = null; - var lstShortUrl = new List(); - do - { - // Retreiving all entities that are NOT the NextId entity - // (it's the only one in the partion "KEY") - TableQuery rangeQuery = new TableQuery().Where( - filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.NotEqual, "KEY")); - - var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); - lstShortUrl.AddRange(queryResult.Results as List); - token = queryResult.ContinuationToken; - } while (token != null); - return lstShortUrl; - } + // public StorageTableHelper(string storageConnectionString) + // { + // StorageConnectionString = storageConnectionString; + // } + // public CloudStorageAccount CreateStorageAccountFromConnectionString() + // { + // CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageConnectionString); + // return storageAccount; + // } + + // private CloudTable GetTable(string tableName) + // { + // CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(); + // CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration()); + // CloudTable table = tableClient.GetTableReference(tableName); + // table.CreateIfNotExists(); + + // return table; + // } + // private CloudTable GetUrlsTable() + // { + // CloudTable table = GetTable("UrlsDetails"); + // return table; + // } + + // private CloudTable GetStatsTable() + // { + // CloudTable table = GetTable("ClickStats"); + // return table; + // } + + // public async Task GetShortUrlEntity(ShortUrlEntity row) + // { + // TableOperation selOperation = TableOperation.Retrieve(row.PartitionKey, row.RowKey); + // TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); + // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; + // return eShortUrl; + // } + + // public async Task> GetAllShortUrlEntities() + // { + // var tblUrls = GetUrlsTable(); + // TableContinuationToken token = null; + // var lstShortUrl = new List(); + // do + // { + // // Retreiving all entities that are NOT the NextId entity + // // (it's the only one in the partion "KEY") + // TableQuery rangeQuery = new TableQuery().Where( + // filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.NotEqual, "KEY")); + + // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); + // lstShortUrl.AddRange(queryResult.Results as List); + // token = queryResult.ContinuationToken; + // } while (token != null); + // return lstShortUrl; + // } /// /// Returns the ShortUrlEntity of the /// /// /// ShortUrlEntity - public async Task GetShortUrlEntityByVanity(string vanity) - { - var tblUrls = GetUrlsTable(); - TableContinuationToken token = null; - ShortUrlEntity shortUrlEntity = null; - do - { - TableQuery query = new TableQuery().Where( - filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, vanity)); - var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(query, token); - shortUrlEntity = queryResult.Results.FirstOrDefault(); - } while (token != null); - - return shortUrlEntity; - } - public async Task SaveClickStatsEntity(ClickStatsEntity newStats) - { - TableOperation insOperation = TableOperation.InsertOrMerge(newStats); - TableResult result = await GetStatsTable().ExecuteAsync(insOperation); - } - - public async Task SaveShortUrlEntity(ShortUrlEntity newShortUrl) - { + // public async Task GetShortUrlEntityByVanity(string vanity) + // { + // var tblUrls = GetUrlsTable(); + // TableContinuationToken token = null; + // ShortUrlEntity shortUrlEntity = null; + // do + // { + // TableQuery query = new TableQuery().Where( + // filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, vanity)); + // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(query, token); + // shortUrlEntity = queryResult.Results.FirstOrDefault(); + // } while (token != null); + + // return shortUrlEntity; + // } + // public async Task SaveClickStatsEntity(ClickStatsEntity newStats) + // { + // TableOperation insOperation = TableOperation.InsertOrMerge(newStats); + // TableResult result = await GetStatsTable().ExecuteAsync(insOperation); + // } + + // public async Task SaveShortUrlEntity(ShortUrlEntity newShortUrl) + // { - // serializing the collection easier on json shares - //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); - - TableOperation insOperation = TableOperation.InsertOrMerge(newShortUrl); - TableResult result = await GetUrlsTable().ExecuteAsync(insOperation); - ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; - return eShortUrl; - } - - public async Task IfShortUrlEntityExistByVanity(string vanity) - { - ShortUrlEntity shortUrlEntity = await GetShortUrlEntityByVanity(vanity); - return (shortUrlEntity != null); - } - - public async Task IfShortUrlEntityExist(ShortUrlEntity row) - { - ShortUrlEntity eShortUrl = await GetShortUrlEntity(row); - return (eShortUrl != null); - } - public async Task GetNextTableId() - { - //Get current ID - TableOperation selOperation = TableOperation.Retrieve("1", "KEY"); - TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); - NextId entity = result.Result as NextId; - - if (entity == null) - { - entity = new NextId - { - PartitionKey = "1", - RowKey = "KEY", - Id = 1024 - }; - } - entity.Id++; - - //Update - TableOperation updOperation = TableOperation.InsertOrMerge(entity); - - // Execute the operation. - await GetUrlsTable().ExecuteAsync(updOperation); - - return entity.Id; - } - - - public async Task UpdateShortUrlEntity(ShortUrlEntity urlEntity) - { - ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); - originalUrl.Url = urlEntity.Url; - originalUrl.Title = urlEntity.Title; - originalUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(urlEntity.Schedules); - - return await SaveShortUrlEntity(originalUrl); - } - - - public async Task> GetAllStatsByVanity(string vanity) - { - var tblUrls = GetStatsTable(); - TableContinuationToken token = null; - var lstShortUrl = new List(); - do - { - TableQuery rangeQuery; - - if(string.IsNullOrEmpty(vanity)){ - rangeQuery = new TableQuery(); - } - else{ - rangeQuery = new TableQuery().Where( - filter: TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, vanity)); - } - - var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); - lstShortUrl.AddRange(queryResult.Results as List); - token = queryResult.ContinuationToken; - } while (token != null); - return lstShortUrl; - } - - - public async Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity) - { - ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); - originalUrl.IsArchived = true; - - return await SaveShortUrlEntity(originalUrl); - } + // // serializing the collection easier on json shares + // //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); + + // TableOperation insOperation = TableOperation.InsertOrMerge(newShortUrl); + // TableResult result = await GetUrlsTable().ExecuteAsync(insOperation); + // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; + // return eShortUrl; + // } + + // public async Task IfShortUrlEntityExistByVanity(string vanity) + // { + // ShortUrlEntity shortUrlEntity = await GetShortUrlEntityByVanity(vanity); + // return (shortUrlEntity != null); + // } + + // public async Task IfShortUrlEntityExist(ShortUrlEntity row) + // { + // ShortUrlEntity eShortUrl = await GetShortUrlEntity(row); + // return (eShortUrl != null); + // } + // public async Task GetNextTableId() + // { + // //Get current ID + // TableOperation selOperation = TableOperation.Retrieve("1", "KEY"); + // TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); + // NextId entity = result.Result as NextId; + + // if (entity == null) + // { + // entity = new NextId + // { + // PartitionKey = "1", + // RowKey = "KEY", + // Id = 1024 + // }; + // } + // entity.Id++; + + // //Update + // TableOperation updOperation = TableOperation.InsertOrMerge(entity); + + // // Execute the operation. + // await GetUrlsTable().ExecuteAsync(updOperation); + + // return entity.Id; + // } + + + // public async Task UpdateShortUrlEntity(ShortUrlEntity urlEntity) + // { + // ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); + // originalUrl.Url = urlEntity.Url; + // originalUrl.Title = urlEntity.Title; + // originalUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(urlEntity.Schedules); + + // return await SaveShortUrlEntity(originalUrl); + // } + + + // public async Task> GetAllStatsByVanity(string vanity) + // { + // var tblUrls = GetStatsTable(); + // TableContinuationToken token = null; + // var lstShortUrl = new List(); + // do + // { + // TableQuery rangeQuery; + + // if(string.IsNullOrEmpty(vanity)){ + // rangeQuery = new TableQuery(); + // } + // else{ + // rangeQuery = new TableQuery().Where( + // filter: TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, vanity)); + // } + + // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); + // lstShortUrl.AddRange(queryResult.Results as List); + // token = queryResult.ContinuationToken; + // } while (token != null); + // return lstShortUrl; + // } + + + // public async Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity) + // { + // ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); + // originalUrl.IsArchived = true; + + // return await SaveShortUrlEntity(originalUrl); + // } } } \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs index 7efe3fa53..a181ddd1a 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs @@ -1,10 +1,12 @@ using System; +using System.Text.Json; +using Azure; using Azure.Data.Tables; using Cloud5mins.ShortenerTools.Core.Domain; namespace Cloud5mins.ShortenerTools.Core.Service; -public class AzStrorageTablesService(TableServiceClient client): IAzStrorageTablesService +public class AzStrorageTablesService(TableServiceClient client) : IAzStrorageTablesService { private TableClient GetUrlsTable() @@ -14,14 +16,44 @@ private TableClient GetUrlsTable() return table; } - public async Task> GetAllShortUrlEntities() + private TableClient GetStatsTable() + { + TableClient table = client.GetTableClient("ClickStats"); + return table; + } + + public async Task GetNextTableId() + { + //Get current ID + TableClient tblUrls = GetUrlsTable(); + var result = await tblUrls.GetEntityAsync("1", "KEY"); + NextId entity = result.Value as NextId; + + if (entity == null) + { + entity = new NextId + { + PartitionKey = "1", + RowKey = "KEY", + Id = 1024 + }; + } + entity.Id++; + + //Update + await tblUrls.UpsertEntityAsync(entity); + + return entity.Id; + } + + public async Task> GetAllShortUrlEntities() { TableClient tblUrls = GetUrlsTable(); - var lstShortUrl = new List(); + var lstShortUrl = new List(); // Retreiving all entities that are NOT the NextId entity // (it's the only one in the partion "KEY") - var queryResult = tblUrls.QueryAsync(e => e.RowKey != "KEY"); + var queryResult = tblUrls.QueryAsync(e => e.RowKey != "KEY"); await foreach (var emp in queryResult.AsPages()) { @@ -35,23 +67,109 @@ public async Task> GetAllShortUrlEntities() } - public async Task SaveShortUrlEntity(ShortUrlEntity2 newShortUrl) - { - - // serializing the collection easier on json shares - //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); + public async Task SaveShortUrlEntity(ShortUrlEntity newShortUrl) + { + + // serializing the collection easier on json shares + //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); + + TableClient tblUrls = GetUrlsTable(); + var response = await tblUrls.UpsertEntityAsync(newShortUrl); + + var temp = response.Content; + return newShortUrl; + } + + public async Task UpdateShortUrlEntity(ShortUrlEntity urlEntity) + { + ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); + originalUrl.Url = urlEntity.Url; + originalUrl.Title = urlEntity.Title; + originalUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(urlEntity.Schedules); + + return await SaveShortUrlEntity(originalUrl); + } + + + /// + /// Returns the ShortUrlEntity of the + /// + /// + /// ShortUrlEntity + public async Task GetShortUrlEntityByVanity(string vanity) + { + var tblUrls = GetUrlsTable(); + ShortUrlEntity shortUrlEntity = null; + + var result = tblUrls.QueryAsync(e => e.RowKey == vanity); + await foreach (var entity in result) + { + shortUrlEntity = entity; + break; + } + return shortUrlEntity; + } + + public async Task GetShortUrlEntity(ShortUrlEntity row) + { + TableClient tblUrls = GetUrlsTable(); + var response = await tblUrls.GetEntityAsync(row.PartitionKey, row.RowKey); + ShortUrlEntity eShortUrl = response.Value as ShortUrlEntity; + return eShortUrl; + } + + + public async Task IfShortUrlEntityExistByVanity(string vanity) + { + ShortUrlEntity shortUrlEntity = await GetShortUrlEntityByVanity(vanity); + return (shortUrlEntity != null); + } + + + public async Task IfShortUrlEntityExist(ShortUrlEntity row) + { + ShortUrlEntity eShortUrl = await GetShortUrlEntity(row); + return (eShortUrl != null); + } + + public async Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity) + { + ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); + originalUrl.IsArchived = true; + + return await SaveShortUrlEntity(originalUrl); + } + + + public async Task> GetAllStatsByVanity(string vanity) + { + var tblStats = GetStatsTable(); + + var lstUrlStats = new List(); + AsyncPageable queryResult; - TableClient tblUrls = GetUrlsTable(); - var response = await tblUrls.AddEntityAsync(newShortUrl); + if (string.IsNullOrEmpty(vanity)) + { + queryResult = tblStats.QueryAsync(); + } + else + { + queryResult = tblStats.QueryAsync(e => e.PartitionKey == vanity); + } - var temp = response.Content; - return newShortUrl; - + await foreach (var emp in queryResult.AsPages()) + { + foreach (var item in emp.Values) + { + lstUrlStats.Add(item); + } + } - // TableOperation insOperation = TableOanything peration.InsertOrMerge(newShortUrl); - // TableResult result = await GetUrlsTable().ExecuteAsync(insOperation); - // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; - // return eShortUrl; - } + return lstUrlStats; + } + public async Task SaveClickStatsEntity(ClickStatsEntity newStats) + { + var result = await GetStatsTable().UpsertEntityAsync(newStats); + } } diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs b/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs index 790667926..1641bc589 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/IAzStrorageTablesService.cs @@ -6,7 +6,15 @@ namespace Cloud5mins.ShortenerTools.Core.Service; public interface IAzStrorageTablesService { - Task> GetAllShortUrlEntities(); - Task SaveShortUrlEntity(ShortUrlEntity2 newRow2); - + Task GetNextTableId(); + Task> GetAllShortUrlEntities(); + Task SaveShortUrlEntity(ShortUrlEntity newRow2); + Task GetShortUrlEntity(ShortUrlEntity row); + Task IfShortUrlEntityExist(ShortUrlEntity row); + Task UpdateShortUrlEntity(ShortUrlEntity urlEntity); + Task GetShortUrlEntityByVanity(string vanity); + Task IfShortUrlEntityExistByVanity(string vanity); + Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity); + Task> GetAllStatsByVanity(string vanity); + Task SaveClickStatsEntity(ClickStatsEntity newStats); } diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs index cc93976e9..a8383e063 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/UrlServices.cs @@ -1,6 +1,7 @@ using Cloud5mins.ShortenerTools; using Cloud5mins.ShortenerTools.Core.Domain; using Cloud5mins.ShortenerTools.Core.Messages; +using Cloud5mins.ShortenerTools.Core.Service; using Microsoft.Extensions.Logging; using System.Net; @@ -9,15 +10,14 @@ namespace Cloud5mins.ShortenerTools.Core.Services; public class UrlServices { private readonly ILogger _logger; - private readonly StorageTableHelper _stgHelper; + private readonly IAzStrorageTablesService _stgHelper; - public UrlServices(ILogger logger) + public UrlServices(ILogger logger, IAzStrorageTablesService stgHelper) { _logger = logger; + _stgHelper = stgHelper; } - private StorageTableHelper StgHelper => _stgHelper ?? new StorageTableHelper(Environment.GetEnvironmentVariable("DataStorage")!); - public async Task Archive(ShortUrlEntity input) { ShortUrlEntity result = await _stgHelper.ArchiveShortUrlEntity(input); @@ -33,14 +33,14 @@ public async Task Redirect(string shortUrl) redirectUrl = Environment.GetEnvironmentVariable("DefaultRedirectUrl") ?? redirectUrl; var tempUrl = new ShortUrlEntity(string.Empty, shortUrl); - var newUrl = await StgHelper.GetShortUrlEntity(tempUrl); + var newUrl = await _stgHelper.GetShortUrlEntity(tempUrl); if (newUrl != null) { _logger.LogInformation($"Found it: {newUrl.Url}"); newUrl.Clicks++; - await StgHelper.SaveClickStatsEntity(new ClickStatsEntity(newUrl.RowKey)); - await StgHelper.SaveShortUrlEntity(newUrl); + await _stgHelper.SaveClickStatsEntity(new ClickStatsEntity(newUrl.RowKey)); + await _stgHelper.SaveShortUrlEntity(newUrl); redirectUrl = WebUtility.UrlDecode(newUrl.ActiveUrl); } } @@ -65,7 +65,7 @@ public async Task List(string host) try { - result.UrlList = await StgHelper.GetAllShortUrlEntities(); + result.UrlList = await _stgHelper.GetAllShortUrlEntities(); result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); foreach (ShortUrlEntity url in result.UrlList) { @@ -110,17 +110,17 @@ public async Task Create(ShortRequest input, string host) { newRow = new ShortUrlEntity(longUrl, vanity, title, input.Schedules); - if (await StgHelper.IfShortUrlEntityExist(newRow)) + if (await _stgHelper.IfShortUrlEntityExist(newRow)) { throw new ShortenerToolException(HttpStatusCode.Conflict, "This Short URL already exist."); } } else { - newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, StgHelper), title, input.Schedules); + newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, _stgHelper), title, input.Schedules); } - await StgHelper.SaveShortUrlEntity(newRow); + await _stgHelper.SaveShortUrlEntity(newRow); result = new ShortResponse(host, newRow.Url, newRow.RowKey, newRow.Title); @@ -153,7 +153,7 @@ public async Task Update(ShortUrlEntity input, string host) throw new ShortenerToolException(HttpStatusCode.BadRequest, $"{input.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'."); } - result = await StgHelper.UpdateShortUrlEntity(input); + result = await _stgHelper.UpdateShortUrlEntity(input); result.ShortUrl = Utility.GetShortUrl(host, result.RowKey); } @@ -172,7 +172,7 @@ public async Task ClickStatsByDay(UrlClickStatsRequest input, str var result = new ClickDateList(); try { - var rawStats = await StgHelper.GetAllStatsByVanity(input.Vanity); + var rawStats = await _stgHelper.GetAllStatsByVanity(input.Vanity); result.Items = rawStats.GroupBy(s => DateTime.Parse(s.Datetime).Date) .Select(stat => new ClickDate diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs index 0c3b319bf..5f0ae5fc3 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/Utility.cs @@ -7,6 +7,7 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; +using Cloud5mins.ShortenerTools.Core.Service; @@ -20,7 +21,7 @@ public static class Utility //sets the length of the unique code to add to vanity private const int MinVanityCodeLength = 5; - public static async Task GetValidEndUrl(string vanity, IStorageTableHelper stgHelper) + public static async Task GetValidEndUrl(string vanity, IAzStrorageTablesService stgHelper) { if (string.IsNullOrEmpty(vanity)) { diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs index 6af27ac1c..114f7eb43 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs @@ -6,18 +6,20 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Cloud5mins.ShortenerTools.Core.Service; namespace Cloud5mins.ShortenerTools.Functions { public class UrlRedirect { private readonly ILogger _logger; + private IAzStrorageTablesService _stgHelper; - - public UrlRedirect(ILoggerFactory loggerFactory) + public UrlRedirect(ILoggerFactory loggerFactory, IAzStrorageTablesService stgHelper) { _logger = loggerFactory.CreateLogger(); _logger.LogInformation("UrlRedirect in constructor"); + _stgHelper = stgHelper; } [Function("UrlRedirect")] @@ -28,7 +30,7 @@ public async Task Run( ExecutionContext context) { _logger.LogInformation("Function reached"); - UrlServices UrlServices = new UrlServices(_logger); + UrlServices UrlServices = new UrlServices(_logger, _stgHelper); _logger.LogInformation("Services created"); _logger.LogInformation($"Redirecting {shortUrl}"); string redirectUrl = await UrlServices.Redirect(shortUrl); diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs index 454d85200..6e68fedce 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Program.cs @@ -3,6 +3,8 @@ var builder = FunctionsApplication.CreateBuilder(args); +builder.AddAzureTableClient("strTables"); + builder.ConfigureFunctionsWebApplication(); // Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4. From 9559684b0183fff285d54a6d349add6f01786771 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 12 Feb 2025 17:03:42 -0500 Subject: [PATCH 12/41] API all done --- ...Cloud5mins.ShortenerTools.Functions.csproj | 31 --- .../Functions/UrlArchive.cs | 98 --------- .../Functions/UrlClickStatsByDay.cs | 108 ---------- .../Functions/UrlCreate.cs | 140 ------------- .../Functions/UrlList.cs | 77 ------- .../Functions/UrlRedirect.cs | 61 ------ .../Functions/UrlUpdate.cs | 124 ----------- .../Program.cs | 35 ---- .../Utility.cs | 71 ------- .../host.json | 16 -- .../local.settings.example.json | 13 -- .../Cloud5mins.ShortenerTools.Api.http | 65 +++++- src/Cloud5mins.ShortenerTools.Api/Program.cs | 7 - .../ShortenerEnpoints.cs | 192 ++++++++++-------- .../WeatherService.cs | 25 --- .../Domain/StorageTableHelper.cs | 191 ----------------- .../Service/AzStrorageTablesService.cs | 6 +- .../Functions/UrlRedirect.cs | 9 +- 18 files changed, 174 insertions(+), 1095 deletions(-) delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Program.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/host.json delete mode 100644 src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json delete mode 100644 src/Cloud5mins.ShortenerTools.Api/WeatherService.cs delete mode 100644 src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj b/src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj deleted file mode 100644 index 658f65fb5..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Cloud5mins.ShortenerTools.Functions.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - net8.0 - v4 - Exe - enabled - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - - - - \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs deleted file mode 100644 index 0e35338af..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlArchive.cs +++ /dev/null @@ -1,98 +0,0 @@ -/* -```c# -Input: - { - // [Required] - "PartitionKey": "d", - - // [Required] - "RowKey": "doc", - - // [Optional] all other properties - } -Output: - { - "Url": "https://docs.microsoft.com/en-ca/azure/azure-functions/functions-create-your-first-function-visual-studio", - "Title": "My Title", - "ShortUrl": null, - "Clicks": 0, - "IsArchived": true, - "PartitionKey": "a", - "RowKey": "azFunc2", - "Timestamp": "2020-07-23T06:22:33.852218-04:00", - "ETag": "W/\"datetime'2020-07-23T10%3A24%3A51.3440526Z'\"" - } - -*/ - -using Cloud5mins.ShortenerTools.Core.Domain; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Net; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - public class UrlArchive - { - - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlArchive(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlArchive")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/UrlArchive")] HttpRequestData req, - ExecutionContext context) - { - _logger.LogInformation($"HTTP trigger - UrlArchive"); - - string userId = string.Empty; - ShortUrlEntity input; - ShortUrlEntity result; - try - { - // Validation of the inputs - if (req == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - - using (var reader = new StreamReader(req.Body)) - { - var body = await reader.ReadToEndAsync(); - input = JsonSerializer.Deserialize(body, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - if (input == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - } - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - result = await stgHelper.ArchiveShortUrlEntity(input); - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error was encountered."); - var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); - await badRequest.WriteAsJsonAsync(new { ex.Message }); - return badRequest; - } - - var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(result); - return response; - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs deleted file mode 100644 index e64299e56..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlClickStatsByDay.cs +++ /dev/null @@ -1,108 +0,0 @@ -/* -```c# -Input: - - { - // [Required] the end of the URL that you want statistics for. - "vanity": "azFunc" - } - -Output: - { - "items": [ - { - "dateClicked": "2020-12-19", - "count": 1 - }, - { - "dateClicked": "2020-12-03", - "count": 2 - } - ], - "url": ""https://c5m.ca/29" -*/ - -using Cloud5mins.ShortenerTools.Core.Domain; -using Cloud5mins.ShortenerTools.Core.Messages; -using Google.Protobuf.WellKnownTypes; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - public class UrlClickStatsByDay - { - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlClickStatsByDay(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlClickStatsByDay")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/UrlClickStatsByDay")] HttpRequestData req, - ExecutionContext context) - { - _logger.LogInformation($"HTTP trigger: UrlClickStatsByDay"); - - string userId = string.Empty; - UrlClickStatsRequest input; - var result = new ClickDateList(); - - // Validation of the inputs - if (req == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - - try - { - using (var reader = new StreamReader(req.Body)) - { - var strBody = await reader.ReadToEndAsync(); - input = JsonSerializer.Deserialize(strBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - if (input == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - } - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - var rawStats = await stgHelper.GetAllStatsByVanity(input.Vanity); - - result.Items = rawStats.GroupBy(s => DateTime.Parse(s.Datetime).Date) - .Select(stat => new ClickDate - { - DateClicked = stat.Key.ToString("yyyy-MM-dd"), - Count = stat.Count() - }).OrderBy(s => DateTime.Parse(s.DateClicked).Date).ToList(); - - var host = string.IsNullOrEmpty(_settings.CustomDomain) ? req.Url.Host : _settings.CustomDomain.ToString(); - result.Url = Utility.GetShortUrl(host, input.Vanity); - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error was encountered."); - var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); - await badRequest.WriteAsJsonAsync(new { Message = $"{ex.Message}" }); - return badRequest; - } - - var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(result); - return response; - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs deleted file mode 100644 index 80373f9de..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlCreate.cs +++ /dev/null @@ -1,140 +0,0 @@ -/* -```c# -Input: - - { - // [Required] The url you wish to have a short version for - "url": "https://docs.microsoft.com/en-ca/azure/azure-functions/functions-create-your-first-function-visual-studio", - - // [Optional] Title of the page, or text description of your choice. - "title": "Quickstart: Create your first function in Azure using Visual Studio" - - // [Optional] the end of the URL. If nothing one will be generated for you. - "vanity": "azFunc" - } - -Output: - { - "ShortUrl": "http://c5m.ca/azFunc", - "LongUrl": "https://docs.microsoft.com/en-ca/azure/azure-functions/functions-create-your-first-function-visual-studio" - } -*/ - -using Cloud5mins.ShortenerTools.Core.Domain; -using Cloud5mins.ShortenerTools.Core.Messages; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Net; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - - public class UrlCreate - { - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlCreate(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlCreate")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "api/UrlCreate")] HttpRequestData req, - ExecutionContext context - ) - { - _logger.LogInformation($"__trace creating shortURL: {req}"); - string userId = string.Empty; - ShortRequest input; - var result = new ShortResponse(); - - try - { - // Validation of the inputs - if (req == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - - using (var reader = new StreamReader(req.Body)) - { - var strBody = await reader.ReadToEndAsync(); - input = JsonSerializer.Deserialize(strBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - if (input == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - } - - // If the Url parameter only contains whitespaces or is empty return with BadRequest. - if (string.IsNullOrWhiteSpace(input.Url)) - { - var badResponse = req.CreateResponse(HttpStatusCode.BadRequest); - await badResponse.WriteAsJsonAsync(new { Message = "The url parameter can not be empty." }); - return badResponse; - } - - // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com - if (!Uri.IsWellFormedUriString(input.Url, UriKind.Absolute)) - { - var badResponse = req.CreateResponse(HttpStatusCode.BadRequest); - await badResponse.WriteAsJsonAsync(new { Message = $"{input.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'." }); - return badResponse; - } - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - string longUrl = input.Url.Trim(); - string vanity = string.IsNullOrWhiteSpace(input.Vanity) ? "" : input.Vanity.Trim(); - string title = string.IsNullOrWhiteSpace(input.Title) ? "" : input.Title.Trim(); - - - ShortUrlEntity newRow; - - if (!string.IsNullOrEmpty(vanity)) - { - newRow = new ShortUrlEntity(longUrl, vanity, title, input.Schedules); - if (await stgHelper.IfShortUrlEntityExist(newRow)) - { - var badResponse = req.CreateResponse(HttpStatusCode.Conflict); - await badResponse.WriteAsJsonAsync(new { Message = "This Short URL already exist." }); - return badResponse; - } - } - else - { - newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, input.Schedules); - } - - await stgHelper.SaveShortUrlEntity(newRow); - - var host = string.IsNullOrEmpty(_settings.CustomDomain) ? req.Url.Host : _settings.CustomDomain.ToString(); - result = new ShortResponse(host, newRow.Url, newRow.RowKey, newRow.Title); - - _logger.LogInformation("Short Url created."); - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error was encountered."); - - var badResponse = req.CreateResponse(HttpStatusCode.BadRequest); - await badResponse.WriteAsJsonAsync(new { ex.Message }); - return badResponse; - } - - var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(result); - - return response; - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs deleted file mode 100644 index 97c78e819..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlList.cs +++ /dev/null @@ -1,77 +0,0 @@ -/* -```c# -Input: - - -Output: - { - "Url": "https://SOME_URL", - "Clicks": 0, - "PartitionKey": "d", - "title": "Quickstart: Create your first function in Azure using Visual Studio" - "RowKey": "doc", - "Timestamp": "0001-01-01T00:00:00+00:00", - "ETag": "W/\"datetime'2020-05-06T14%3A33%3A51.2639969Z'\"" - } -*/ - -using Cloud5mins.ShortenerTools.Core.Domain; -using Cloud5mins.ShortenerTools.Core.Messages; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - public class UrlList - { - - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlList(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlList")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "api/UrlList")] HttpRequestData req, ExecutionContext context) - { - _logger.LogInformation($"Starting UrlList..."); - - var result = new ListResponse(); - string userId = string.Empty; - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - try - { - result.UrlList = await stgHelper.GetAllShortUrlEntities(); - result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList(); - var host = string.IsNullOrEmpty(_settings.CustomDomain) ? req.Url.Host : _settings.CustomDomain; - foreach (ShortUrlEntity url in result.UrlList) - { - url.ShortUrl = Utility.GetShortUrl(host, url.RowKey); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error was encountered."); - var badres = req.CreateResponse(HttpStatusCode.BadRequest); - await badres.WriteAsJsonAsync(new { ex.Message }); - return badres; - } - - var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(result); - return response; - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs deleted file mode 100644 index f73788817..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlRedirect.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Cloud5mins.ShortenerTools.Core.Domain; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - public class UrlRedirect - { - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlRedirect(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlRedirect")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "{shortUrl}")] - HttpRequestData req, - string shortUrl, - ExecutionContext context) - { - string redirectUrl = "https://azure.com"; - - - if (!string.IsNullOrWhiteSpace(shortUrl)) - { - redirectUrl = _settings.DefaultRedirectUrl ?? redirectUrl; - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - var tempUrl = new ShortUrlEntity(string.Empty, shortUrl); - var newUrl = await stgHelper.GetShortUrlEntity(tempUrl); - - if (newUrl != null) - { - _logger.LogInformation($"Found it: {newUrl.Url}"); - newUrl.Clicks++; - await stgHelper.SaveClickStatsEntity(new ClickStatsEntity(newUrl.RowKey)); - await stgHelper.SaveShortUrlEntity(newUrl); - redirectUrl = WebUtility.UrlDecode(newUrl.ActiveUrl); - } - } - else - { - _logger.LogInformation("Bad Link, resorting to fallback."); - } - - var res = req.CreateResponse(HttpStatusCode.Redirect); - res.Headers.Add("Location", redirectUrl); - return res; - - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs deleted file mode 100644 index 7d839e2c6..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Functions/UrlUpdate.cs +++ /dev/null @@ -1,124 +0,0 @@ -/* -```c# -Input: - { - // [Required] - "PartitionKey": "d", - - // [Required] - "RowKey": "doc", - - // [Optional] New Title for this URL, or text description of your choice. - "title": "Quickstart: Create your first function in Azure using Visual Studio" - - // [Optional] New long Url where the the user will be redirect - "Url": "https://SOME_URL" - } - - -Output: - { - "Url": "https://SOME_URL", - "Clicks": 0, - "PartitionKey": "d", - "title": "Quickstart: Create your first function in Azure using Visual Studio" - "RowKey": "doc", - "Timestamp": "0001-01-01T00:00:00+00:00", - "ETag": "W/\"datetime'2020-05-06T14%3A33%3A51.2639969Z'\"" - } -*/ - -using Cloud5mins.ShortenerTools.Core.Domain; -// using Microsoft.Azure.WebJobs; -// using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Net; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Cloud5mins.ShortenerTools.Functions -{ - public class UrlUpdate - { - private readonly ILogger _logger; - private readonly ShortenerSettings _settings; - - public UrlUpdate(ILoggerFactory loggerFactory, ShortenerSettings settings) - { - _logger = loggerFactory.CreateLogger(); - _settings = settings; - } - - [Function("UrlUpdate")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/UrlUpdate")] HttpRequestData req, - ExecutionContext context - ) - { - _logger.LogInformation($"HTTP trigger - UrlUpdate"); - - string userId = string.Empty; - ShortUrlEntity input; - ShortUrlEntity result; - - try - { - // Validation of the inputs - if (req == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - - using (var reader = new StreamReader(req.Body)) - { - var strBody = await reader.ReadToEndAsync(); - input = JsonSerializer.Deserialize(strBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - if (input == null) - { - return req.CreateResponse(HttpStatusCode.NotFound); - } - } - - // If the Url parameter only contains whitespaces or is empty return with BadRequest. - if (string.IsNullOrWhiteSpace(input.Url)) - { - var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); - await badRequest.WriteAsJsonAsync(new { Message = "The url parameter can not be empty." }); - return badRequest; - } - - // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com - if (!Uri.IsWellFormedUriString(input.Url, UriKind.Absolute)) - { - var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); - await badRequest.WriteAsJsonAsync(new { Message = $"{input.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'." }); - return badRequest; - } - - StorageTableHelper stgHelper = new StorageTableHelper(_settings.DataStorage); - - result = await stgHelper.UpdateShortUrlEntity(input); - var host = string.IsNullOrEmpty(_settings.CustomDomain) ? req.Url.Host : _settings.CustomDomain.ToString(); - result.ShortUrl = Utility.GetShortUrl(host, result.RowKey); - - } - catch (Exception ex) - { - _logger.LogError(ex, "An unexpected error was encountered."); - - var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); - await badRequest.WriteAsJsonAsync(new { ex.Message }); - return badRequest; - } - - var response = req.CreateResponse(HttpStatusCode.OK); - await response.WriteAsJsonAsync(result); - return response; - } - } -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Program.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Program.cs deleted file mode 100644 index 5248902bd..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Cloud5mins.ShortenerTools.Core.Domain; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Cloud5mins.ShortenerTools -{ - public class Program - { - public static void Main() - { - ShortenerSettings shortenerSettings = null; - - var host = new HostBuilder() - .ConfigureFunctionsWebApplication() - .ConfigureServices((context, services) => - { - // Add our global configuration instance - services.AddSingleton(options => - { - var configuration = context.Configuration; - shortenerSettings = new ShortenerSettings(); - configuration.Bind(shortenerSettings); - return configuration; - }); - - // Add our configuration class - services.AddSingleton(options => { return shortenerSettings; }); - }) - .Build(); - - host.Run(); - } - } -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs b/src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs deleted file mode 100644 index dc1a26b2d..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/Utility.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Cloud5mins.ShortenerTools.Core.Domain; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System; -using System.Linq; -using System.Net; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Threading.Tasks; - - - -namespace Cloud5mins.ShortenerTools -{ - public static class Utility - { - //reshuffled for randomisation, same unique characters just jumbled up, you can replace with your own version - private const string ConversionCode = "FjTG0s5dgWkbLf_8etOZqMzNhmp7u6lUJoXIDiQB9-wRxCKyrPcv4En3Y21aASHV"; - private static readonly int Base = ConversionCode.Length; - //sets the length of the unique code to add to vanity - private const int MinVanityCodeLength = 5; - - public static async Task GetValidEndUrl(string vanity, StorageTableHelper stgHelper) - { - if (string.IsNullOrEmpty(vanity)) - { - var newKey = await stgHelper.GetNextTableId(); - string getCode() => Encode(newKey); - if (await stgHelper.IfShortUrlEntityExistByVanity(getCode())) - return await GetValidEndUrl(vanity, stgHelper); - - return string.Join(string.Empty, getCode()); - } - else - { - return string.Join(string.Empty, vanity); - } - } - - public static string Encode(int i) - { - if (i == 0) - return ConversionCode[0].ToString(); - - return GenerateUniqueRandomToken(i); - } - - public static string GetShortUrl(string host, string vanity) - { - return host + "/" + vanity; - } - - // generates a unique, random, and alphanumeric token for the use as a url - //(not entirely secure but not sequential so generally not guessable) - public static string GenerateUniqueRandomToken(int uniqueId) - { - using (var generator = RandomNumberGenerator.Create()) - { - //minimum size I would suggest is 5, longer the better but we want short URLs! - var bytes = new byte[MinVanityCodeLength]; - generator.GetBytes(bytes); - var chars = bytes - .Select(b => ConversionCode[b % ConversionCode.Length]); - var token = new string(chars.ToArray()); - var reversedToken = string.Join(string.Empty, token.Reverse()); - return uniqueId + reversedToken; - } - } - } -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/host.json b/src-old/Cloud5mins.ShortenerTools.Functions/host.json deleted file mode 100644 index 5ae5a72c7..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/host.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - }, - "extensions": { - "http": { - "routePrefix": "" - } - } -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json b/src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json deleted file mode 100644 index e4611f63b..000000000 --- a/src-old/Cloud5mins.ShortenerTools.Functions/local.settings.example.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "", - "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "DataStorage":"CONNECTION_STRING_TO_STORAGE" - }, - "Host": { - "LocalHttpPort": 7071, - "CORS": "*", - "CORSCredentials": false - } -} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http index 9b3d4b5ff..18066c951 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -1,17 +1,13 @@ ## @Api_HostAddress = http://localhost:5288 @Api_HostAddress = https://localhost:7187 -### #################################### -### weatherforecast -GET {{Api_HostAddress}}/weatherforecast/ -Accept: application/json ### #################################### ### Get a Welcome message Get {{Api_HostAddress}}/api -### #################################### -### Create a new short URL + +### ERROR no URL POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json @@ -19,11 +15,11 @@ Content-Type: application/json { "Vanity": "1111-test", - "Url": "https://learn.microsoft.com/en-us/dotnet/aspire/storage/azure-storage-blobs-integration?tabs=dotnet-cli", + "Url": "", "Title": ".NET Aspire Azure Blob Storage integration" } -### ERROR no URL +### ERROR URL no valide POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json @@ -31,11 +27,12 @@ Content-Type: application/json { "Vanity": "1111-test", - "Url": "", + "Url": "That's not a URL", "Title": ".NET Aspire Azure Blob Storage integration" } -### ERROR URL no valide +### #################################### +### Create a new short URL POST {{Api_HostAddress}}/api/UrlCreate Accept: application/json @@ -43,7 +40,7 @@ Content-Type: application/json { "Vanity": "1111-test", - "Url": "That's not a URL", + "Url": "https://learn.microsoft.com/en-us/dotnet/aspire/storage/azure-storage-blobs-integration?tabs=dotnet-cli", "Title": ".NET Aspire Azure Blob Storage integration" } @@ -55,3 +52,49 @@ Accept: application/json +### #################################### +### Update existing URL + +POST {{Api_HostAddress}}/api/UrlUpdate +Accept: application/json +Content-Type: application/json + +{ + "partitionKey": "1", + "rowKey": "1111-test", + "Vanity": "1111-test", + "Url": "https://learn.microsoft.com", + "Title": "Microsoft Learn", + "Archived": false +} + + + +### #################################### +### Archive a new short URL + +POST {{Api_HostAddress}}/api/UrlArchive +Accept: application/json +Content-Type: application/json + +{ + "partitionKey": "1", + "rowKey": "1111-test", + "Vanity": "1111-test", + "Url": "https://learn.microsoft.com/en-us/dotnet/aspire/storage/azure-storage-blobs-integration?tabs=dotnet-cli", + "Title": ".NET Aspire Azure Blob Storage integration" +} + + + + +### #################################### +### Get Stats + +POST {{Api_HostAddress}}/api/UrlClickStatsByDay +Accept: application/json +Content-Type: application/json + +{ + "Vanity": "1111-test" +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Api/Program.cs b/src/Cloud5mins.ShortenerTools.Api/Program.cs index 82381ac26..e2f8b286c 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Program.cs +++ b/src/Cloud5mins.ShortenerTools.Api/Program.cs @@ -17,8 +17,6 @@ return loggerFactory.CreateLogger("shortenerLogger"); }); -//to remove just while migration -builder.Services.AddSingleton(); // Register WeatherService var app = builder.Build(); @@ -30,14 +28,9 @@ app.MapOpenApi(); } - - app.UseHttpsRedirection(); app.MapShortenerEnpoints(); -app.MapGet("/weatherforecast", (WeatherService weatherService) => weatherService.GetWeatherForecast()) - .WithName("GetWeatherForecast"); - app.Run(); diff --git a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs index 600713b29..9e36a99e6 100644 --- a/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs +++ b/src/Cloud5mins.ShortenerTools.Api/ShortenerEnpoints.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Net; using System.Threading.Tasks.Dataflow; using System.Xml.Linq; using Azure.Data.Tables; @@ -6,6 +7,7 @@ using Cloud5mins.ShortenerTools.Core.Domain; using Cloud5mins.ShortenerTools.Core.Messages; using Cloud5mins.ShortenerTools.Core.Service; +using Cloud5mins.ShortenerTools.Core.Services; using Microsoft.AspNetCore.Http.HttpResults; public static class ShortenerEnpoints @@ -15,17 +17,33 @@ public static void MapShortenerEnpoints(this IEndpointRouteBuilder app) var endpoints = app.MapGroup("api") .WithOpenApi(); + // GETS + endpoints.MapGet("/", GetWelcomeMessage) .WithDescription("Welcome to Cloud5mins URL Shortener API"); + endpoints.MapGet("/UrlList", UrlList) + .WithDescription("List all Urls") + .WithDisplayName("Url List"); + + + // POSTS + endpoints.MapPost("/UrlCreate", UrlCreate) .WithDescription("Create a new Short URL") .WithDisplayName("Url Create"); - endpoints.MapGet("/UrlList", UrlList) - .WithDescription("List all Urls") - .WithDisplayName("Url List"); + endpoints.MapPost("/UrlUpdate", UrlUpdate) + .WithDescription("Update a Url") + .WithDisplayName("Url Update"); + + endpoints.MapPost("/UrlArchive", UrlArchive) + .WithDescription("Archive a Url") + .WithDisplayName("Url Archive"); + endpoints.MapPost("/UrlClickStatsByDay", UrlClickStatsByDay) + .WithDescription("Provide Click Statistics by Day") + .WithDisplayName("Url Click Statistics By Day"); } static private string GetWelcomeMessage() @@ -39,112 +57,124 @@ static private async Task, Conflict, InternalServerError - >> UrlCreate(ShortRequest request, - TableServiceClient tblClient, - HttpContext context, + >> UrlCreate(ShortRequest request, + TableServiceClient tblClient, + HttpContext context, ILogger logger) { - - logger.LogTrace($"creating shortURL: {request.Url}"); - var result = new ShortResponse(); - IAzStrorageTablesService stgHelper = new AzStrorageTablesService(tblClient); - try { - // If the Url parameter only contains whitespaces or is empty return with BadRequest. - if (string.IsNullOrWhiteSpace(request.Url)) - { - string ErrorMsg = "The url parameter cannot be empty."; - logger.LogInformation(ErrorMsg); - return TypedResults.NotFound(new DetailedBadRequest { Message = ErrorMsg }); - } - - // Validates if input.url is a valid aboslute url, aka is a complete refrence to the resource, ex: http(s)://google.com - if (!Uri.IsWellFormedUriString(request.Url, UriKind.Absolute)) - { - string ErrorMsg = $"{request.Url} is not a valid absolute Url. The Url parameter must start with 'http://' or 'http://'."; - logger.LogInformation(ErrorMsg); - return TypedResults.BadRequest(new DetailedBadRequest { Message = ErrorMsg }); - } - - string longUrl = request.Url.Trim(); - string vanity = string.IsNullOrWhiteSpace(request.Vanity) ? "" : request.Vanity.Trim(); - string title = string.IsNullOrWhiteSpace(request.Title) ? "" : request.Title.Trim(); - - ShortUrlEntity? newRow; - - if (!string.IsNullOrEmpty(vanity)) - { - newRow = new ShortUrlEntity(longUrl, vanity, title, request.Schedules); - if (await stgHelper.IfShortUrlEntityExist(newRow)) - { - // throw new Exception("This Short URL already exist."); - string ErrorMsg = "This Short URL already exist."; - logger.LogInformation(ErrorMsg); - return TypedResults.Conflict(new DetailedBadRequest { Message = ErrorMsg }); - } - } - else + var urlServices = new UrlServices(logger, new AzStrorageTablesService(tblClient)); + var host = GetHost(context); + ShortResponse result = await urlServices.Create(request, host); + return TypedResults.Created($"/api/UrlCreate/{result.ShortUrl}", result); + } + catch (ShortenerToolException ex) + { + switch (ex.StatusCode) { - newRow = new ShortUrlEntity(longUrl, await Utility.GetValidEndUrl(vanity, stgHelper), title, request.Schedules); + case HttpStatusCode.BadRequest: + return TypedResults.BadRequest(new DetailedBadRequest { Message = ex.Message }); + case HttpStatusCode.NotFound: + return TypedResults.NotFound(new DetailedBadRequest { Message = ex.Message }); + case HttpStatusCode.Conflict: + return TypedResults.Conflict(new DetailedBadRequest { Message = ex.Message }); + default: + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); } + } + catch (Exception ex) + { + logger.LogError(ex, "An unexpected error was encountered."); + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); + } + } - await stgHelper.SaveShortUrlEntity(newRow); + static private async Task>> + UrlArchive(ShortUrlEntity shortUrl, + TableServiceClient tblClient, + ILogger logger) + { + try + { + var urlServices = new UrlServices(logger, new AzStrorageTablesService(tblClient)); + var result = await urlServices.Archive(shortUrl); + return TypedResults.Ok(); + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); + } + } + static private async Task, + InternalServerError>> + UrlUpdate(ShortUrlEntity shortUrl, + TableServiceClient tblClient, + HttpContext context, + ILogger logger) + { + try + { + var urlServices = new UrlServices(logger, new AzStrorageTablesService(tblClient)); var host = GetHost(context); - result = new ShortResponse(host!, newRow.Url, newRow.RowKey, newRow.Title); - - logger.LogTrace("Short Url created."); - - return TypedResults.Created($"/api/UrlCreate/{result.ShortUrl}", result); + var result = await urlServices.Update(shortUrl, host); + return TypedResults.Ok(result); } catch (Exception ex) { - Console.WriteLine($"A unexpected error was encountered: {ex.Message}"); logger.LogError(ex.Message); return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); } } + static private async Task, - InternalServerError - >> UrlList( TableServiceClient tblClient, + Ok, + InternalServerError>> + UrlClickStatsByDay(UrlClickStatsRequest statsRequest, + TableServiceClient tblClient, HttpContext context, ILogger logger) { - logger.LogTrace("Starting UrlList..."); - - var result = new ListResponse(); - string userId = string.Empty; - IAzStrorageTablesService stgHelper = new AzStrorageTablesService(tblClient); - try { - List allUrls = await stgHelper.GetAllShortUrlEntities(); - var filtredUlrs = allUrls.Where(p => !(p.IsArchived ?? false)).ToList(); + var urlServices = new UrlServices(logger, new AzStrorageTablesService(tblClient)); + var host = GetHost(context); + var result = await urlServices.ClickStatsByDay(statsRequest, host); + return TypedResults.Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return TypedResults.InternalServerError(new DetailedBadRequest { Message = ex.Message }); + } + } - // Insert into result.UrlList all filtredUlrs mapping all properties - result.UrlList = filtredUlrs.Select(p => new ShortUrlEntity - { - PartitionKey = p.PartitionKey, - RowKey = p.RowKey, - Url = p.Url, - Title = p.Title, - Clicks = p.Clicks, - IsArchived = p.IsArchived, - Schedules = p.Schedules - }).ToList(); - var host = GetHost(context); - foreach (ShortUrlEntity url in result.UrlList) - { - url.ShortUrl = Utility.GetShortUrl(host, url.RowKey); - } - return TypedResults.Ok(result); + + + + static private async Task, + InternalServerError>> + UrlList(TableServiceClient tblClient, + HttpContext context, + ILogger logger) + { + try + { + var urlServices = new UrlServices(logger, new AzStrorageTablesService(tblClient)); + var host = GetHost(context); + ListResponse Urls = await urlServices.List(host); + return TypedResults.Ok(Urls); } catch (Exception ex) { diff --git a/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs b/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs deleted file mode 100644 index 609a8e2e3..000000000 --- a/src/Cloud5mins.ShortenerTools.Api/WeatherService.cs +++ /dev/null @@ -1,25 +0,0 @@ -public class WeatherService -{ - private static readonly string[] summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - public WeatherForecast[] GetWeatherForecast() - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - } -} - -public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs deleted file mode 100644 index de20b5d83..000000000 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/StorageTableHelper.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Microsoft.Azure.Cosmos.Table; -using System.Text.Json; - -namespace Cloud5mins.ShortenerTools.Core.Domain -{ - public class StorageTableHelper//: IStorageTableHelper - { - private string StorageConnectionString { get; set; } - - public StorageTableHelper() { } - - // public StorageTableHelper(string storageConnectionString) - // { - // StorageConnectionString = storageConnectionString; - // } - // public CloudStorageAccount CreateStorageAccountFromConnectionString() - // { - // CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageConnectionString); - // return storageAccount; - // } - - // private CloudTable GetTable(string tableName) - // { - // CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(); - // CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration()); - // CloudTable table = tableClient.GetTableReference(tableName); - // table.CreateIfNotExists(); - - // return table; - // } - // private CloudTable GetUrlsTable() - // { - // CloudTable table = GetTable("UrlsDetails"); - // return table; - // } - - // private CloudTable GetStatsTable() - // { - // CloudTable table = GetTable("ClickStats"); - // return table; - // } - - // public async Task GetShortUrlEntity(ShortUrlEntity row) - // { - // TableOperation selOperation = TableOperation.Retrieve(row.PartitionKey, row.RowKey); - // TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); - // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; - // return eShortUrl; - // } - - // public async Task> GetAllShortUrlEntities() - // { - // var tblUrls = GetUrlsTable(); - // TableContinuationToken token = null; - // var lstShortUrl = new List(); - // do - // { - // // Retreiving all entities that are NOT the NextId entity - // // (it's the only one in the partion "KEY") - // TableQuery rangeQuery = new TableQuery().Where( - // filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.NotEqual, "KEY")); - - // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); - // lstShortUrl.AddRange(queryResult.Results as List); - // token = queryResult.ContinuationToken; - // } while (token != null); - // return lstShortUrl; - // } - - /// - /// Returns the ShortUrlEntity of the - /// - /// - /// ShortUrlEntity - // public async Task GetShortUrlEntityByVanity(string vanity) - // { - // var tblUrls = GetUrlsTable(); - // TableContinuationToken token = null; - // ShortUrlEntity shortUrlEntity = null; - // do - // { - // TableQuery query = new TableQuery().Where( - // filter: TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, vanity)); - // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(query, token); - // shortUrlEntity = queryResult.Results.FirstOrDefault(); - // } while (token != null); - - // return shortUrlEntity; - // } - // public async Task SaveClickStatsEntity(ClickStatsEntity newStats) - // { - // TableOperation insOperation = TableOperation.InsertOrMerge(newStats); - // TableResult result = await GetStatsTable().ExecuteAsync(insOperation); - // } - - // public async Task SaveShortUrlEntity(ShortUrlEntity newShortUrl) - // { - - // // serializing the collection easier on json shares - // //newShortUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(newShortUrl.Schedules); - - // TableOperation insOperation = TableOperation.InsertOrMerge(newShortUrl); - // TableResult result = await GetUrlsTable().ExecuteAsync(insOperation); - // ShortUrlEntity eShortUrl = result.Result as ShortUrlEntity; - // return eShortUrl; - // } - - // public async Task IfShortUrlEntityExistByVanity(string vanity) - // { - // ShortUrlEntity shortUrlEntity = await GetShortUrlEntityByVanity(vanity); - // return (shortUrlEntity != null); - // } - - // public async Task IfShortUrlEntityExist(ShortUrlEntity row) - // { - // ShortUrlEntity eShortUrl = await GetShortUrlEntity(row); - // return (eShortUrl != null); - // } - // public async Task GetNextTableId() - // { - // //Get current ID - // TableOperation selOperation = TableOperation.Retrieve("1", "KEY"); - // TableResult result = await GetUrlsTable().ExecuteAsync(selOperation); - // NextId entity = result.Result as NextId; - - // if (entity == null) - // { - // entity = new NextId - // { - // PartitionKey = "1", - // RowKey = "KEY", - // Id = 1024 - // }; - // } - // entity.Id++; - - // //Update - // TableOperation updOperation = TableOperation.InsertOrMerge(entity); - - // // Execute the operation. - // await GetUrlsTable().ExecuteAsync(updOperation); - - // return entity.Id; - // } - - - // public async Task UpdateShortUrlEntity(ShortUrlEntity urlEntity) - // { - // ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); - // originalUrl.Url = urlEntity.Url; - // originalUrl.Title = urlEntity.Title; - // originalUrl.SchedulesPropertyRaw = JsonSerializer.Serialize>(urlEntity.Schedules); - - // return await SaveShortUrlEntity(originalUrl); - // } - - - // public async Task> GetAllStatsByVanity(string vanity) - // { - // var tblUrls = GetStatsTable(); - // TableContinuationToken token = null; - // var lstShortUrl = new List(); - // do - // { - // TableQuery rangeQuery; - - // if(string.IsNullOrEmpty(vanity)){ - // rangeQuery = new TableQuery(); - // } - // else{ - // rangeQuery = new TableQuery().Where( - // filter: TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, vanity)); - // } - - // var queryResult = await tblUrls.ExecuteQuerySegmentedAsync(rangeQuery, token); - // lstShortUrl.AddRange(queryResult.Results as List); - // token = queryResult.ContinuationToken; - // } while (token != null); - // return lstShortUrl; - // } - - - // public async Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity) - // { - // ShortUrlEntity originalUrl = await GetShortUrlEntity(urlEntity); - // originalUrl.IsArchived = true; - - // return await SaveShortUrlEntity(originalUrl); - // } - } -} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs index a181ddd1a..4e5995278 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Service/AzStrorageTablesService.cs @@ -18,6 +18,7 @@ private TableClient GetUrlsTable() private TableClient GetStatsTable() { + client.CreateTableIfNotExists("ClickStats"); TableClient table = client.GetTableClient("ClickStats"); return table; } @@ -128,8 +129,9 @@ public async Task IfShortUrlEntityExistByVanity(string vanity) public async Task IfShortUrlEntityExist(ShortUrlEntity row) { - ShortUrlEntity eShortUrl = await GetShortUrlEntity(row); - return (eShortUrl != null); + TableClient tblUrls = GetUrlsTable(); + var result = await tblUrls.GetEntityIfExistsAsync(row.PartitionKey, row.RowKey); + return result.HasValue; } public async Task ArchiveShortUrlEntity(ShortUrlEntity urlEntity) diff --git a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs index 114f7eb43..01e9935b0 100644 --- a/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs +++ b/src/Cloud5mins.ShortenerTools.FunctionsLight/Functions/UrlRedirect.cs @@ -7,19 +7,20 @@ using System.Threading; using System.Threading.Tasks; using Cloud5mins.ShortenerTools.Core.Service; +using Azure.Data.Tables; namespace Cloud5mins.ShortenerTools.Functions { public class UrlRedirect { private readonly ILogger _logger; - private IAzStrorageTablesService _stgHelper; + private TableServiceClient _tblClient; - public UrlRedirect(ILoggerFactory loggerFactory, IAzStrorageTablesService stgHelper) + public UrlRedirect(ILoggerFactory loggerFactory, TableServiceClient tblClient) { _logger = loggerFactory.CreateLogger(); _logger.LogInformation("UrlRedirect in constructor"); - _stgHelper = stgHelper; + _tblClient = tblClient; } [Function("UrlRedirect")] @@ -30,7 +31,7 @@ public async Task Run( ExecutionContext context) { _logger.LogInformation("Function reached"); - UrlServices UrlServices = new UrlServices(_logger, _stgHelper); + UrlServices UrlServices = new UrlServices(_logger, new AzStrorageTablesService(_tblClient)); _logger.LogInformation("Services created"); _logger.LogInformation($"Redirecting {shortUrl}"); string redirectUrl = await UrlServices.Redirect(shortUrl); From 4d9c76ca9e9b4ece83f4b711dd215da7063184e3 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 Feb 2025 11:31:27 -0500 Subject: [PATCH 13/41] UPdate and Archive implemented --- .../Domain/ShortUrlEntity.cs | 10 +++ .../Dialogues/UpdateUrlDialog.razor | 89 +++++++++++++++++++ .../Components/Layout/MainLayout.razor | 2 + .../Components/Pages/UrlManager.razor | 37 ++++++-- .../Program.cs | 2 + .../UrlManagerClient.cs | 34 +++++++ 6 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor diff --git a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs index d9a367c0b..e73d89896 100644 --- a/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs +++ b/src/Cloud5mins.ShortenerTools.Core/Domain/ShortUrlEntity.cs @@ -135,6 +135,16 @@ private string GetActiveUrl(DateTime pointInTime) } return link; } + + public bool Validate() + { + //TODO: Add more validation + if (string.IsNullOrEmpty(Url)) + { + return false; + } + return true; + } } } \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor new file mode 100644 index 000000000..342ecc07e --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor @@ -0,0 +1,89 @@ +@using Cloud5mins.ShortenerTools.Core.Domain; +@using Microsoft.FluentUI.AspNetCore.Components.Components.Tooltip +@implements IDialogContentComponent +@* @inject ITooltipService tooltipService *@ + +@rendermode InteractiveServer + + + + + + @Dialog!.Instance.Parameters.Title + + + + + + + + +
+ + +
+ +
+ + +
+ +
+ + + + The vanity cannot be updated it's The identifier for the short URL. + +
+ +
+ @* *@ +
+ +
+ +
+ + + + + Save + Cancel + + + +@code { + [Parameter] + public ShortUrlEntity Content { get; set; } = default!; + + [CascadingParameter] + public FluentDialog? Dialog { get; set; } = default!; + + private ShortUrlEntity _shortUrl = new ShortUrlEntity(); + + + protected override void OnInitialized() + { + _shortUrl = Content; + } + private void ToggleDialogPrimaryActionButton(bool enable) + { + Dialog!.TogglePrimaryActionButton(enable); + } + + private async Task SaveAsync() + { + if (_shortUrl.Validate()) + { + await Dialog!.CloseAsync(_shortUrl); + } + } + + private async Task CancelAsync() + { + await Dialog!.CancelAsync(); + } +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor index 97f14b6b9..20a76e939 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/MainLayout.razor @@ -23,6 +23,8 @@ + +
An unhandled error has occurred. diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor index 3d983798c..15730a59a 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -7,6 +7,7 @@ @inject UrlManagerClient urlManager @inject IJSRuntime JSRuntime @inject IDialogService DialogService +@inject IToastService toastService @inject NavigationManager NavigationManager @rendermode InteractiveServer @@ -235,6 +236,11 @@ Schedules = dialogData.Schedules.ToArray() }; var response = await urlManager.UrlCreate(urlRequest); + if (response) + { + await UpdateUIList(); + toastService.ShowSuccess("Short URL created successfully"); + } } } @@ -245,10 +251,27 @@ } - void EditShortUrl(ShortUrlEntity urlEntity) + private async Task EditShortUrl(ShortUrlEntity urlEntity) { - editedUrl = urlEntity; - ShowEditPopup = true; + var dialog = await DialogService.ShowDialogAsync(urlEntity, new DialogParameters() + { + Title = $"Update Url: {urlEntity.RowKey}", + PreventDismissOnOverlayClick = true, + PreventScroll = true, + }); + + var result = await dialog.Result; + if (!result.Cancelled && result.Data != null) + { + var dialogData = (ShortUrlEntity)result.Data; + + var response = await urlManager.UrlUpdate(urlEntity); + if (response != null) + { + await UpdateUIList(); + toastService.ShowSuccess("Short URL updated successfully"); + } + } } private async Task SaveUpdatedShortUrl() @@ -264,8 +287,12 @@ public async Task ArchiveShortUrl(ShortUrlEntity urlEntity) { - @* await Http.PostAsJsonAsync("/api/UrlArchive", urlEntity); - await UpdateUIList(); *@ + var result = await urlManager.UrlArchive(urlEntity); + if (result) + { + await UpdateUIList(); + toastService.ShowSuccess("Short URL archived successfully"); + } } } diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs index 91df8d79d..8ecac521f 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs @@ -1,6 +1,7 @@ using Microsoft.FluentUI.AspNetCore.Components; using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components; using Cloud5mins.ShortenerTools.TinyBlazorAdmin; +using Microsoft.FluentUI.AspNetCore.Components.Components.Tooltip; var builder = WebApplication.CreateBuilder(args); @@ -15,6 +16,7 @@ builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddFluentUIComponents(); +builder.Services.AddScoped(); var app = builder.Build(); app.MapDefaultEndpoints(); diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs index f95a2003b..dd66d5a8c 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs @@ -38,4 +38,38 @@ public async Task UrlCreate(ShortRequest url) return false; } + + public async Task UrlArchive(ShortUrlEntity shortUrl) + { + try{ + using var response = await httpClient.PostAsJsonAsync("/api/UrlArchive", shortUrl); + if(response.IsSuccessStatusCode){ + return true; + } + } + catch(Exception ex){ + Console.WriteLine(ex.Message); + } + + return false; + } + + public async Task UrlUpdate(ShortUrlEntity shortUrl) + { + try + { + using var response = await httpClient.PostAsJsonAsync("/api/UrlUpdate", shortUrl); + if (response.IsSuccessStatusCode) + { + var updatedUrl = await response.Content.ReadFromJsonAsync(); + return updatedUrl; + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + + return null; + } } From f29cae438e0c685bd4a14b4f254355212805f8b8 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 Feb 2025 17:33:43 -0500 Subject: [PATCH 14/41] schedule done --- .../Pages/Help.razor | 11 --- .../Components/Dialogues/NewUrlDialog.razor | 41 +++++---- .../Dialogues/UpdateUrlDialog.razor | 39 ++++---- .../Components/Layout/NavMenu.razor | 1 + .../Components/Pages/Counter.razor | 8 ++ .../Components/Pages/Help.razor | 11 +++ .../Components/Pages/Home.razor | 19 +++- .../Components/Pages/UrlManager.razor | 10 +- .../Components/Shared/ScheduleComponent.razor | 87 ++++++++++++++++++ .../Shared/SchedulesComponent.razor | 53 +++++++++++ .../wwwroot/images/TinyBlazorAdmin.png | Bin 0 -> 61551 bytes 11 files changed, 227 insertions(+), 53 deletions(-) delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Help.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/ScheduleComponent.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/SchedulesComponent.razor create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor deleted file mode 100644 index af0f04997..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Help.razor +++ /dev/null @@ -1,11 +0,0 @@ -@page "/help" - -Tiny Blazor Admin - Help - -

Help

-
-
    -
  • You need to be authenticated to have access. By default Active Directory is the one used (any outlook.com). Others (GitHub, Twitter, etc.) are also available to used.
  • -
  • You need to be part or the role admin (all lowercase) to have access. This is manage from the Azure Portal, in the Role management blade of your Azure Static Web App. Refer to the documentation for more details.
  • -
-
\ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor index 8ab04d5e4..6c84cd9e3 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/NewUrlDialog.razor @@ -1,4 +1,5 @@ @using Cloud5mins.ShortenerTools.Core.Messages +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components.Shared @implements IDialogContentComponent @rendermode InteractiveServer @@ -16,24 +17,28 @@ -
- - -
- -
- - -
- -
- - -
- -
- @* *@ -
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor index 342ecc07e..14c05788e 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Dialogues/UpdateUrlDialog.razor @@ -1,7 +1,7 @@ @using Cloud5mins.ShortenerTools.Core.Domain; +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components.Shared @using Microsoft.FluentUI.AspNetCore.Components.Components.Tooltip @implements IDialogContentComponent -@* @inject ITooltipService tooltipService *@ @rendermode InteractiveServer @@ -18,28 +18,31 @@ -
- - -
+ -
- - -
+
+ + +
-
- +
+ + +
- - The vanity cannot be updated it's The identifier for the short URL. - -
+ + -
- @* *@ -
+ + The vanity cannot be updated it's The identifier for the short URL. + +
+
+ +
+ +
diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor index 271dd0d74..3f1f4b51e 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor @@ -9,6 +9,7 @@ Counter URL Manager Settings + Help
diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor index c7623ca7c..e8abd9b6b 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor @@ -1,4 +1,6 @@ @page "/counter" +@using Cloud5mins.ShortenerTools.Core.Domain +@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components.Shared @rendermode InteractiveServer Counter @@ -9,9 +11,15 @@ Click me +
+ +
+ @code { private int currentCount = 0; + private ShortUrlEntity editedUrl = new ShortUrlEntity(); + private void IncrementCount() { currentCount++; diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Help.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Help.razor new file mode 100644 index 000000000..034e46f5b --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Help.razor @@ -0,0 +1,11 @@ +@page "/help" + +Tiny Blazor Admin - Help + +

Help

+ +
    +
  • You need to be authenticated to have access. By default Active Directory is the one used (any outlook.com). Others (GitHub, Twitter, etc.) are also available to used.
  • +
  • You need to be part or the role admin (all lowercase) to have access. This is manage from the Azure Portal, in the Role management blade of your Azure Static Web App. Refer to the documentation for more details.
  • +
+
\ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor index 96714a213..a17c52259 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Home.razor @@ -1,7 +1,20 @@ @page "/" -Home +Tiny Blazor Admin -

Hello, world!

+

Tiny Blazor Admin

+
+

+ Welcome to the Tiny Blazor Admin +

-Welcome to your new Fluent Blazor app. \ No newline at end of file +
+

This is the frontend of a solution using AzUrlShortener both projects are available on GitHub.

+ +

This site is build using .Net Blazor Web Assembly and some Blazor components from Syncfusion (thank you for the Community licence)

+ +

If you want to learn more about Tiny Blazor Admin please refer to the documentation.

+
+ +
+
\ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor index 15730a59a..6dda12eb4 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -32,7 +32,7 @@
- @(context!.Schedules?.Count ?? 0) + @(context!.Schedules?.Count ?? 0) @context!.RowKey @@ -40,8 +40,10 @@ - @context.Clicks - + + @context.Clicks + + @@ -222,6 +224,7 @@ Title = "Create a new Short Url", PreventDismissOnOverlayClick = true, PreventScroll = true, + Width = "600px" }); var result = await dialog.Result; @@ -258,6 +261,7 @@ Title = $"Update Url: {urlEntity.RowKey}", PreventDismissOnOverlayClick = true, PreventScroll = true, + Width = "600px" }); var result = await dialog.Result; diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/ScheduleComponent.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/ScheduleComponent.razor new file mode 100644 index 000000000..c4348957f --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/ScheduleComponent.razor @@ -0,0 +1,87 @@ +@inherits LayoutComponentBase +@using Cloud5mins.ShortenerTools.Core.Domain + +@rendermode InteractiveServer + + + +
+ + +
+
+ + +
+
+
+ +
+
+ + + Tool: https://crontab.guru/ + + +
+
+ +
+
+ + +@code { + + [Parameter] + public Schedule? schedule { get; set; } + + private DateTime startDate; + private DateTime startTime; + private DateTime endDate; + private DateTime endTime; + + private enum DateTimeSection + { + Date, + Time + } + + protected override void OnInitialized() + { + startDate = schedule.Start; + startTime = schedule.Start; + + endDate = schedule.End; + endTime = schedule.End; + } + + private void StartDateTimeReconcile(DateTimeSection section, DateTime value) + { + if (section == DateTimeSection.Date) + { + startDate = value; + } + else + { + startTime = value; + } + + var dateReconciled = new DateTime(startDate.Year, startDate.Month, startDate.Day, startTime.Hour, startTime.Minute, 0); + schedule.Start = dateReconciled; + } + + private void EndDateTimeReconcile(DateTimeSection section, DateTime value) + { + if (section == DateTimeSection.Date) + { + endDate = value; + } + else + { + endTime = value; + } + + var dateReconciled = new DateTime(endDate.Year, endDate.Month, endDate.Day, endTime.Hour, endTime.Minute, 0); + schedule.End = dateReconciled; + } +} diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/SchedulesComponent.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/SchedulesComponent.razor new file mode 100644 index 000000000..45428eeab --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Shared/SchedulesComponent.razor @@ -0,0 +1,53 @@ +@inherits LayoutComponentBase +@using Cloud5mins.ShortenerTools +@using Cloud5mins.ShortenerTools.Core.Domain + + + + + Schedules + + + + + + @foreach(var s in schedules) + { + + + + + + @s.Start.ToString("yyyy-MM-dd") < + @s.GetDisplayableUrl(25) > + @s.End.ToString("yyyy-MM-dd") + + + + + + + + } + + + + + +@code { + + [Parameter] + public List schedules { get; set; } + + + private void AddScheduleClick(){ + schedules.Add(new Schedule()); + StateHasChanged(); + } + + private void DeleteSchedule(Schedule schedule){ + schedules.Remove(schedule); + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png new file mode 100644 index 0000000000000000000000000000000000000000..535a427eb7b0a02a7d973efa06120505e299a8d3 GIT binary patch literal 61551 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

=Ug{R@}hi43}CNZ8@sO-p6)EpTb-nsz_Hw{)7zX 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 QK>}V%3TpDTvZlfR1I3YPL;wH) literal 0 HcmV?d00001 From 034fe0d5fbe7ced2ea1e8343a447dc6b22a44b06 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 Feb 2025 18:20:05 -0500 Subject: [PATCH 15/41] Adding stats --- .../Cloud5mins.ShortenerTools.Api.http | 12 +- ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 2 + .../Components/Layout/NavMenu.razor | 1 + .../Components/Pages/Statistics.razor | 105 ++++++++++++++ .../Components/Pages/UrlManager.razor | 128 +----------------- .../Program.cs | 6 + .../UrlManagerClient.cs | 19 +++ 7 files changed, 145 insertions(+), 128 deletions(-) create mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Statistics.razor diff --git a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http index 18066c951..27f20df8b 100644 --- a/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http +++ b/src/Cloud5mins.ShortenerTools.Api/Cloud5mins.ShortenerTools.Api.http @@ -97,4 +97,14 @@ Content-Type: application/json { "Vanity": "1111-test" -} \ No newline at end of file +} + + +### #################################### +### Get All Stats + +POST {{Api_HostAddress}}/api/UrlClickStatsByDay +Accept: application/json +Content-Type: application/json + +{} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj index 4f31885ac..bfafa5172 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor index 3f1f4b51e..7dbf1a89a 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Layout/NavMenu.razor @@ -8,6 +8,7 @@ Home Counter URL Manager + Statistics Settings Help diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Statistics.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Statistics.razor new file mode 100644 index 000000000..9dbe66f64 --- /dev/null +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Statistics.razor @@ -0,0 +1,105 @@ +@page "/statistics" +@page "/statistics/{vanity}" + +@using Cloud5mins.ShortenerTools.Core.Messages; +@using Cloud5mins.ShortenerTools.Core.Domain +@using Syncfusion.Blazor.Charts +@using Syncfusion.Blazor.Spinner +@using Microsoft.AspNetCore.Authorization +@using System.Collections.ObjectModel +@using System.Text.Json +@using System.Net +@inject IJSRuntime JSRuntime +@inject UrlManagerClient urlManager + + +

Click Statistics

+

@subTitle

+ << Back + +
+@if(clicksHistory != null){ + + + + + + + + + + + + + + + + + + + + +} +else{ + +} +
+
+

@((MarkupString)dayCount)

+
+ +@code { + + [Parameter] +#nullable enable + public string? vanity { get; set; } +#nullable disable + + private bool isLoading { get; set; } = true; + private string subTitle = ""; + private ClickDateList clickStatsList; + private ObservableCollection clicksHistory; + private readonly Random _random = new Random(); + private string dayCount = string.Empty; + + public class ClickData + { +#nullable enable + public string? XValue; +#nullable disable + public int YValue; + } + + private async Task> UpdateUIList() + { + subTitle = (!String.IsNullOrEmpty(vanity))? $"Clicks for: {vanity}": "All clicks"; + try{ + + var response = await urlManager.UrlClickStatsByDay( new UrlClickStatsRequest(vanity)); + if(response != null){ + return new ObservableCollection(response.Items); + } + } + catch (System.Exception ex) + { + Console.WriteLine(ex.ToString()); + } + return null; + } + + protected override async void OnInitialized() + { + clicksHistory = await UpdateUIList(); + this.isLoading = false; + StateHasChanged(); + if(clicksHistory != null) + dayCount = "Day(s): " + clicksHistory.Count.ToString(); + + } +} \ No newline at end of file diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor index 6dda12eb4..6e61006e3 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/UrlManager.razor @@ -58,106 +58,9 @@ -@if (ShowCreatePopup) -{ - @* *@ - -} - - -@if (ShowEditPopup) -{ - @* *@ - -} @code { - bool ShowCreatePopup = false; - bool ShowEditPopup = false; + private ListResponse urls = new ListResponse(); IQueryable? urlList; @@ -192,33 +95,10 @@ await RefreshGrid(); StateHasChanged(); } - private async Task SaveShortUrl() - { - @* ShowCreatePopup = false; - - try - { - await Http.PostAsJsonAsync("/api/UrlCreate", shortUrlRequest); - await UpdateUIList(); *@ - @* await grdUrls.ClearFilteringAsync(); - await grdUrls.FilterByColumnAsync("RowKey", "equal", shortUrlRequest.Vanity); *@ - @* } - catch (System.Exception ex) - { - Console.WriteLine(ex.ToString()); - } *@ - } - - void ClosePopup() - { - ShowCreatePopup = false; - ShowEditPopup = false; - } private async Task CreateShortUrl() { shortUrlRequest = new ShortUrlRequest(); - //ShowCreatePopup = true; var dialog = await DialogService.ShowDialogAsync(shortUrlRequest, new DialogParameters() { Title = "Create a new Short Url", @@ -278,12 +158,6 @@ } } - private async Task SaveUpdatedShortUrl() - { - @* ShowEditPopup = false; - await Http.PostAsJsonAsync("/api/UrlUpdate", editedUrl); - await UpdateUIList(); *@ - } private void NavigateToStats(string vanity){ NavigationManager.NavigateTo("/Statistics/" + vanity); diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs index 8ecac521f..5c5db97b1 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs @@ -2,6 +2,7 @@ using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Components; using Cloud5mins.ShortenerTools.TinyBlazorAdmin; using Microsoft.FluentUI.AspNetCore.Components.Components.Tooltip; +using Syncfusion.Blazor; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +19,11 @@ builder.Services.AddFluentUIComponents(); builder.Services.AddScoped(); +// regiser fusion blazor service +// Community Licence for your personal use ONLY. Thank you Syncfusion for this generous offer. +Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("NzYyMzI1QDMyMzAyZTMxMmUzMFY0cEZ3MVozdkwvekVhek8xTWdPMkg2NlhvdVFNR1lvZHdhQWJWUlNjZW89"); +builder.Services.AddSyncfusionBlazor(); + var app = builder.Build(); app.MapDefaultEndpoints(); diff --git a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs index dd66d5a8c..0346c5fa1 100644 --- a/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs +++ b/src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/UrlManagerClient.cs @@ -72,4 +72,23 @@ public async Task UrlArchive(ShortUrlEntity shortUrl) return null; } + + public async Task UrlClickStatsByDay(UrlClickStatsRequest statsRequest) + { + try + { + using var response = await httpClient.PostAsJsonAsync("/api/UrlClickStatsByDay", statsRequest); + if (response.IsSuccessStatusCode) + { + var clickStats = await response.Content.ReadFromJsonAsync(); + return clickStats; + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + + return null; + } } From fab03b0af45c8e5db95cb3298b4f83db76640f6a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 14 Feb 2025 16:07:25 -0500 Subject: [PATCH 16/41] First draft completed --- .../App.razor | 16 - ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 22 - .../Pages/Authentication.razor | 7 - .../Pages/Index.razor | 20 - .../Pages/Statistics.razor | 117 ---- .../Pages/UrlManager.razor | 292 ---------- .../Program.cs | 25 - .../Properties/launchSettings.json | 30 - .../README.md | 56 -- .../Shared/401.razor | 3 - .../Shared/404.razor | 3 - .../Shared/Custom401.razor | 29 - .../Shared/Custom404.razor | 18 - .../Shared/MainLayout.razor | 26 - .../Shared/MainLayout.razor.css | 81 --- .../Shared/NavMenu.razor | 50 -- .../Shared/NavMenu.razor.css | 62 -- .../Shared/RedirectToLogin.razor | 8 - .../Shared/ScheduleComponent.razor | 39 -- .../Shared/SchedulesComponent.razor | 62 -- .../_Imports.razor | 14 - .../staticwebapp.config.json | 50 -- .../wwwroot/appsettings.example.json | 13 - .../wwwroot/appsettings.json | 3 - .../wwwroot/css/app.css | 64 --- .../wwwroot/css/bootstrap/bootstrap.min.css | 7 - .../css/bootstrap/bootstrap.min.css.map | 1 - .../wwwroot/css/open-iconic/FONT-LICENSE | 86 --- .../wwwroot/css/open-iconic/ICON-LICENSE | 21 - .../wwwroot/css/open-iconic/README.md | 114 ---- .../font/css/open-iconic-bootstrap.min.css | 1 - .../open-iconic/font/fonts/open-iconic.eot | Bin 28196 -> 0 bytes .../open-iconic/font/fonts/open-iconic.otf | Bin 20996 -> 0 bytes .../open-iconic/font/fonts/open-iconic.svg | 543 ------------------ .../open-iconic/font/fonts/open-iconic.ttf | Bin 28028 -> 0 bytes .../open-iconic/font/fonts/open-iconic.woff | Bin 14984 -> 0 bytes .../wwwroot/favicon-16x16.png | Bin 740 -> 0 bytes .../wwwroot/favicon-32x32.png | Bin 1909 -> 0 bytes .../wwwroot/favicon.ico | Bin 15406 -> 0 bytes .../wwwroot/images/TinyBlazorAdmin.png | Bin 61551 -> 0 bytes .../wwwroot/index.html | 42 -- .../Cloud5mins.ShortenerTools.Api.http | 44 +- .../ShortenerEnpoints.cs | 37 +- .../Domain/ClickDate.cs | 2 +- .../Service/AzStrorageTablesService.cs | 11 +- .../Service/UrlServices.cs | 9 +- ...mins.ShortenerTools.TinyBlazorAdmin.csproj | 4 +- .../Components/App.razor | 11 + .../Components/Layout/NavMenu.razor | 1 - .../Components/Pages/Counter.razor | 27 - .../Components/Pages/Settings.razor | 6 +- .../Components/Pages/Statistics.razor | 44 +- .../Components/Pages/UrlManager.razor | 18 +- .../Program.cs | 2 +- .../UrlManagerClient.cs | 15 +- 55 files changed, 156 insertions(+), 2000 deletions(-) delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/FONT-LICENSE delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/ICON-LICENSE delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/README.md delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.eot delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.otf delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.svg delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/open-iconic/font/fonts/open-iconic.woff delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-16x16.png delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon-32x32.png delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/favicon.ico delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/images/TinyBlazorAdmin.png delete mode 100644 src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/index.html delete mode 100644 src/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Components/Pages/Counter.razor diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor deleted file mode 100644 index 6033da94a..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/App.razor +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - Not found - - - - diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj deleted file mode 100644 index 12b3e5d1c..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Cloud5mins.ShortenerTools.TinyBlazorAdmin.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net8.0 - disable - enable - Cloud5mins.ShortenerTools.TinyBlazorAdmin - portable - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor deleted file mode 100644 index 6c7435673..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Authentication.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/authentication/{action}" -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication - - -@code{ - [Parameter] public string? Action { get; set; } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor deleted file mode 100644 index a17c52259..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Index.razor +++ /dev/null @@ -1,20 +0,0 @@ -@page "/" - -Tiny Blazor Admin - -

Tiny Blazor Admin

-
-

- Welcome to the Tiny Blazor Admin -

- -
-

This is the frontend of a solution using AzUrlShortener both projects are available on GitHub.

- -

This site is build using .Net Blazor Web Assembly and some Blazor components from Syncfusion (thank you for the Community licence)

- -

If you want to learn more about Tiny Blazor Admin please refer to the documentation.

-
- -
-
\ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor deleted file mode 100644 index f08c3151c..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/Statistics.razor +++ /dev/null @@ -1,117 +0,0 @@ -@page "/statistics" -@page "/statistics/{vanity}" - -@using Cloud5mins.ShortenerTools.Core.Messages; -@using Cloud5mins.ShortenerTools.Core.Domain -@using Syncfusion.Blazor.Charts -@using Syncfusion.Blazor.Spinner -@using Microsoft.AspNetCore.Authorization -@using System.Collections.ObjectModel -@using System.Text.Json -@using System.Net -@inject IJSRuntime JSRuntime -@inject HttpClient Http -@inject NavigationManager NavigationManager - -@attribute [Authorize(Roles = "admin")] - -

Click Statistics

-

@subTitle

- << Back - -
-@if(clicksHistory != null){ - - - - - - - - - - - - - - - - - - - - -} -else{ - -} -
-
-

@((MarkupString)dayCount)

-
- -@code { - - [Parameter] -#nullable enable - public string? vanity { get; set; } -#nullable disable - - private bool isLoading { get; set; } = true; - private string subTitle = ""; - private ClickDateList clickStatsList; - private ObservableCollection clicksHistory; - private readonly Random _random = new Random(); - private string dayCount = string.Empty; - - public class ClickData - { -#nullable enable - public string? XValue; -#nullable disable - public int YValue; - } - - private async Task> UpdateUIList() - { - subTitle = (!String.IsNullOrEmpty(vanity))? $"Clicks for: {vanity}": "All clicks"; - try{ - CancellationToken cancellationToken = new CancellationToken(); - using var response = await Http.PostAsJsonAsync("/api/UrlClickStatsByDay", new UrlClickStatsRequest(vanity), cancellationToken); - if(response.IsSuccessStatusCode){ - var jsonResult = await response.Content.ReadAsStringAsync(); - clickStatsList = JsonSerializer.Deserialize(jsonResult); - - return new ObservableCollection(clickStatsList.Items); - } - switch (response.StatusCode) - { - case HttpStatusCode.Unauthorized: NavigationManager.NavigateTo("/unauthorized"); - break; - default: NavigationManager.NavigateTo("/404"); - break; - } - } - catch (System.Exception ex) - { - Console.WriteLine(ex.ToString()); - } - return null; - } - - protected override async void OnInitialized() - { - clicksHistory = await UpdateUIList(); - this.isLoading = false; - StateHasChanged(); - if(clicksHistory != null) - dayCount = "Day(s): " + clicksHistory.Count.ToString(); - - } -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor deleted file mode 100644 index 293cf0f8b..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Pages/UrlManager.razor +++ /dev/null @@ -1,292 +0,0 @@ -@page "/urlmanager" - -@using Cloud5mins.ShortenerTools.Core.Domain -@using Cloud5mins.ShortenerTools.Core.Messages; -@using Microsoft.AspNetCore.Authorization -@using Syncfusion.Blazor.Grids -@using Syncfusion.Blazor.Inputs -@using System.Net -@inject HttpClient Http -@inject IJSRuntime JSRuntime -@inject NavigationManager NavigationManager - -@attribute [Authorize(Roles = "admin")] - -Url Manager - -

Urls Manager

-

Create, Edit, Achives your URLs

- -@if (urls.UrlList == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - - - - - - - - - - - - - - -} - - -@if (ShowCreatePopup) -{ - - -} - - -@if (ShowEditPopup) -{ - - -} - -@code { - bool ShowCreatePopup = false; - bool ShowEditPopup = false; - private ListResponse urls = new ListResponse(); - ShortUrlRequest shortUrlRequest = new ShortUrlRequest(); - ShortUrlEntity editedUrl; - SfGrid grdUrls; - public List gridData { get; set; } - - - protected override async Task OnInitializedAsync() - { - await RefreshGrid(); - } - - private async Task RefreshGrid() - { - try - { - using var response = await Http.GetAsync("/api/UrlList"); - if(response.IsSuccessStatusCode){ - urls = await response.Content.ReadFromJsonAsync(); - } - else{ - switch (response.StatusCode) - { - case HttpStatusCode.Unauthorized: NavigationManager.NavigateTo("/unauthorized"); - break; - default: NavigationManager.NavigateTo("/404"); - break; - } - } - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } - } - - private async Task UpdateUIList() - { - await RefreshGrid(); - StateHasChanged(); - } - private async Task SaveShortUrl() - { - ShowCreatePopup = false; - - try - { - await Http.PostAsJsonAsync("/api/UrlCreate", shortUrlRequest); - await UpdateUIList(); - @* await grdUrls.ClearFilteringAsync(); - await grdUrls.FilterByColumnAsync("RowKey", "equal", shortUrlRequest.Vanity); *@ - } - catch (System.Exception ex) - { - Console.WriteLine(ex.ToString()); - } - } - - void ClosePopup() - { - ShowCreatePopup = false; - ShowEditPopup = false; - } - - [Authorize(Roles = "admin")] - void CreateShortUrl() - { - shortUrlRequest = new ShortUrlRequest(); - ShowCreatePopup = true; - } - - [Inject] public IJSRuntime JsRuntime { get; set; } - public async Task CopyToClipboardAsync(string url) - { - await JSRuntime.InvokeVoidAsync("clipboardCopy.copyText", url); - } - - - void EditShortUrl(ShortUrlEntity urlEntity) - { - editedUrl = urlEntity; - ShowEditPopup = true; - } - - private async Task SaveUpdatedShortUrl() - { - ShowEditPopup = false; - await Http.PostAsJsonAsync("/api/UrlUpdate", editedUrl); - await UpdateUIList(); - } - - private void NavigateToStats(string vanity){ - NavigationManager.NavigateTo("/Statistics/" + vanity); - } - - public async Task ArchiveShortUrl(ShortUrlEntity urlEntity) - { - await Http.PostAsJsonAsync("/api/UrlArchive", urlEntity); - await UpdateUIList(); - } - -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs deleted file mode 100644 index 1ca8d8f05..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Syncfusion.Blazor; -using Cloud5mins.ShortenerTools.TinyBlazorAdmin; -using AzureStaticWebApps.Blazor.Authentication; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); -var baseAddress = builder.HostEnvironment.BaseAddress; -builder.Services - .AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }) - .AddStaticWebAppsAuthentication(); - -// builder.Services.AddMsalAuthentication(options => -// { -// builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); -// }); - -// regiser fusion blazor service -// Community Licence for your personal use ONLY. Thank you Syncfusion for this generous offer. -Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("NzYyMzI1QDMyMzAyZTMxMmUzMFY0cEZ3MVozdkwvekVhek8xTWdPMkg2NlhvdVFNR1lvZHdhQWJWUlNjZW89"); -builder.Services.AddSyncfusionBlazor(); - -await builder.Build().RunAsync(); diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json deleted file mode 100644 index 87ef6d9af..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:46660", - "sslPort": 44350 - } - }, - "profiles": { - "Cloud5mins.ShortenerTools.TinyBlazorAdmin": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md deleted file mode 100644 index 099d03309..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# TinyBlazorAdmin - -[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) - - - Admin tools for [Azure Url Shortener](https://github.com/microsoft/AzUrlShortener) using [Blazor Single Page Application (webassembly)](https://azure.microsoft.com/services/app-service/static/?WT.mc_id=dotnet-0000-frbouche). - -The project is now at version 3 and ready to be used! It is using Azure Static Web App native security and the API is an Azure Function. - -![Tiny Blazor Admin home page][tinyBA_home] - -Once authenticated you can manage your URLs and see some statistics. Thanks to [Syncfusion](https://www.syncfusion.com/blazor-components) for the community licences. Everyone can use Tiny Blazor Admin with that great look! - -![Tiny Blazor Admin URLs manager page][tinyBA_urls] - -![Tiny Blazor Admin Statistics page][tinyBA_stats] - - -# Deployment - -Until an automatic deployment is created, you will need to deploy some part manually. [All the steps to deploy the TinyBlazorAdmin app into Azure are listed here](https://github.com/microsoft/AzUrlShortener/wiki/How-to-deploy-TinyBlazorAdmin). You can also run it somewhere else if you prefer, even locally. - -# Contributing - -If you find a bug or would like to add a feature, check out those resources: - -[tinyBA_home]: /Media/tinyBA_home.png -[tinyBA_stats]: /Media/tinyBA_stats.png -[tinyBA_urls]: /Media/tinyBA_urls.png - -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - -
Frank Boucher
Frank Boucher

📖 💻 🤔
jbrule
jbrule

📖
Christos Matskas
Christos Matskas

🛡️ 🐛
Ron Howe
Ron Howe

📖
Mark Phillipson
Mark Phillipson

📖 💻 👀
fatpacket
fatpacket

📖
- - - - - - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor deleted file mode 100644 index 86e134e10..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/401.razor +++ /dev/null @@ -1,3 +0,0 @@ -@page "/unauthorized" -Tiny Blazor Admin - 401 - \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor deleted file mode 100644 index aff36a095..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/404.razor +++ /dev/null @@ -1,3 +0,0 @@ -Tiny Blazor Admin - 404 -@page "/404" - \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor deleted file mode 100644 index 67ee64754..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom401.razor +++ /dev/null @@ -1,29 +0,0 @@ -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication - -
-
-
-
-
-

Oops!

-

401 Unauthorized

- - -
- Sorry, you are not authorized to access this resource. -
-
- -
- Sorry, please get authenticated. -
-
-
- Return Home -
-
-
-
- -
diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor deleted file mode 100644 index da5c4868f..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/Custom404.razor +++ /dev/null @@ -1,18 +0,0 @@ - -
-
-
-
-
-

Oops!

-

404 Not Found

-
- Sorry, an error has occured, Requested page not found! -
- Return Home> -
-
-
-
- -
\ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor deleted file mode 100644 index 3a7e2c507..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor +++ /dev/null @@ -1,26 +0,0 @@ -@inherits LayoutComponentBase - -
- - -
-
- - - Hello, @context.User.Identity?.Name! - Log out - - - Log in - - - About -
- -
- @Body -
-
-
diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css deleted file mode 100644 index c8654276b..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/MainLayout.razor.css +++ /dev/null @@ -1,81 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor deleted file mode 100644 index 2286f7987..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor +++ /dev/null @@ -1,50 +0,0 @@ - - -
- -
- -@code { - private bool collapseNavMenu = true; - -#nullable enable - private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; -#nullable disable - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f81..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor deleted file mode 100644 index 8201c1bb3..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/RedirectToLogin.razor +++ /dev/null @@ -1,8 +0,0 @@ -@inject NavigationManager Navigation - -@code { - protected override void OnInitialized() - { - Navigation.NavigateTo($"/.auth/login/aad?post_login_redirect_uri={Uri.EscapeDataString(Navigation.Uri)}"); - } -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor deleted file mode 100644 index 7cd64f32b..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/ScheduleComponent.razor +++ /dev/null @@ -1,39 +0,0 @@ -@inherits LayoutComponentBase -@using Cloud5mins.ShortenerTools.Core.Domain -@using Syncfusion.Blazor.Calendars -@using Syncfusion.Blazor.Inputs - -
-
-
- -
-
- -
-
-
-
- -
-
-
-
- - Tool: https://crontab.guru/ -
-
-
-
- -
-
-
- - -@code { - - [Parameter] - public Schedule schedule { get; set; } - -} diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor deleted file mode 100644 index ffcb5fd71..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/Shared/SchedulesComponent.razor +++ /dev/null @@ -1,62 +0,0 @@ -@inherits LayoutComponentBase -@using Cloud5mins.ShortenerTools -@using Cloud5mins.ShortenerTools.Core.Domain -@using Syncfusion.Blazor.Buttons -@using Syncfusion.Blazor.Navigations - -
-
- -
- - - - @foreach(var s in schedules) - { - - - - @s.Start.ToString("yyyy-MM-dd") < - @s.GetDisplayableUrl(25) > - @s.End.ToString("yyyy-MM-dd") - - - - - - } - - - -
- - - - - -@code { - - [Parameter] - public List schedules { get; set; } - - - private void AddScheduleClick(){ - schedules.Add(new Schedule()); - StateHasChanged(); - } - - private void DeleteSchedule(Schedule schedule){ - schedules.Remove(schedule); - StateHasChanged(); - } - -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor deleted file mode 100644 index dfaf390e1..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/_Imports.razor +++ /dev/null @@ -1,14 +0,0 @@ -@using System.Net.Http -@using System.Net.Http.Json -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http -@using Microsoft.JSInterop -@using Cloud5mins.ShortenerTools.TinyBlazorAdmin -@using Cloud5mins.ShortenerTools.TinyBlazorAdmin.Shared -@using Syncfusion.Blazor -@using Syncfusion.Blazor.Calendars diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json deleted file mode 100644 index 33093bb6c..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/staticwebapp.config.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "navigationFallback": { - "rewrite": "/index.html" - }, - "routes": [ - { - "route": "/urlmanager*", - "allowedRoles": ["admin"] - }, - { - "route": "/statistics*", - "allowedRoles": ["admin"] - }, - { - "route": "/api/*", - "methods": ["GET", "POST"], - "allowedRoles": ["admin"] - }, - { - "route": "/login", - "rewrite": "/.auth/login/aad" - }, - { - "route": "/.auth/login/twitter", - "statusCode": 404 - }, - { - "route": "/logout*", - "redirect": "/.auth/logout" - } - , - { - "route": "/unauthorized*", - "redirect": "/unauthorized" - } - ], - "responseOverrides": { - "401": { - "redirect": "/.auth/login/aad?post_login_redirect_uri=.referrer", - "statusCode": 302 - }, - "403": { - "rewrite": "/unauthorized" - }, - "404": { - "rewrite": "/404" - } - } - -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json deleted file mode 100644 index f848fb8d7..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.example.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - /* - The following identity settings need to be configured - before the project can be successfully executed. - For more info see https://aka.ms/dotnet-template-ms-identity-platform - */ - "AzureAd": { - "Authority": "https://login.microsoftonline.com/206bad4c-d071-4c91-9181-ef7047e6590b", - "ClientId": "8ff845ba-70f7-479a-a2c5-73f38b650232", - "ValidateAuthority": true - }, - "API_Prefix": "http://localhost:7071" -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json deleted file mode 100644 index 10d137644..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/appsettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "API_Prefix": "http://localhost:7071" -} \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css deleted file mode 100644 index 9cd148f7d..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/app.css +++ /dev/null @@ -1,64 +0,0 @@ -@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); - -html, body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -h1:focus { - outline: none; -} - -a, .btn-link { - color: #0071c1; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - -.content { - padding-top: 1.1rem; -} - -.valid.modified:not([type=checkbox]) { - outline: 1px solid #26b050; -} - -.invalid { - outline: 1px solid red; -} - -.validation-message { - color: red; -} - -#blazor-error-ui { - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; - padding: 1rem 1rem 1rem 3.7rem; - color: white; -} - - .blazor-error-boundary::after { - content: "An error has occurred." - } diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css deleted file mode 100644 index 02ae65b5f..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -@charset "UTF-8";/*! - * Bootstrap v5.1.0 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map b/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map deleted file mode 100644 index afcd9e33e..000000000 --- a/src-old/Cloud5mins.ShortenerTools.TinyBlazorAdmin/wwwroot/css/bootstrap/bootstrap.min.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-`