-
-
-
-
-
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
+# TinyBlazorAdmin
+
+[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
+
+
+ Admin tools for [Azure Url Shortener](https://github.com/FBoucher/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 1 and ready to be used! ~~just getting started but should have a v1 ready in Summer 2020~~. It is using Azure Active Directory (AAD) as authentication for the user and to connect to the API (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][inyBA_urls]
+
+![Tiny Blazor Admin Statistics page][inyBA_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](deployment.md). 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:
+
+To see the current work in progress: [GLO boards](https://app.gitkraken.com/glo/board/XtpDU2ZLuQARV8y7) 'kanban board'
+
+
+[TinyBlazorAdmin]: medias/TinyBlazorAdmin.png
+[tinyBA_home]: medias/tinyBA_home.png
+[inyBA_stats]: medias/inyBA_stats.png
+[inyBA_urls]: medias/inyBA_urls.png
+
+## Contributors ✨
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/deployment.md b/deployment.md
index 4249918..a558470 100644
--- a/deployment.md
+++ b/deployment.md
@@ -1,155 +1,155 @@
-# Deployment
-
-Until a "full automatic" deployment is created, here are all the steps to deploy the TinyBlazorAdmin app into Azure. You can run it somewhere else and even locally.
-
-## First thing first
-
-You need to **fork this repo** into your own account. You will need to update the configuration (this document will explain when and what), therefore it needs to be yours.
-
-To fork a GitHub repository click on the fork button on the top right of the screen. If you need more detail have a look to this GitHub doc: [Fork a repo
-](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo).
-
-## Deploy AzUrlShortener (the Backend)
-
-This project is a frontend only so you will need to deploy the [Azure Url Shortener](https://github.com/FBoucher/AzUrlShortener) in "headless mode". Do to it, click the blue button below and make sure to select **none** as Frontend
-
-[![Deploy Backend to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/?WT.mc_id=urlshortener-github-frbouche#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FFBoucher%2FAzUrlShortener%2Fmain%2Fdeployment%2FazureDeploy.json)
-
-![CreateBackend][CreateBackend]
-
-
-## Deploy TinyBlazorAdmin (the Frontend)
-
-There are many ways you could run Tiny Blazor Admin website. In this deployment, we will use the new [Azure Static Web App (SWA)](https://azure.microsoft.com/en-ca/services/app-service/static/?WT.mc_id=tinyblazoradmin-github-frbouche). However, because the TinyBlazorAdmin use Azure Active Directory (AAD), we need a standalone Azure Function (deployed at the previous step).
-
-Open Azure portal (portal.azure.com), open the **resource group** where you created the backend (ex: streamDemo is our case). Click the "**+**" and search **Static Web App**, and click the *Create* button.
-
-![Creating swa][swa_create1]
-
-> Note: You will need to **Authorize Azure Static Web Apps**, to have access to _your_ GitHub repository (the one created when you forked the project). This is required because SWA uses GitHub Action to deploy.
-
-![Creating swa part 2][swa_create2]
-
-Select your organization, repository and branch (ex: main).
-
-![Creating swa part 3][swa_create3]
-
-Select **Blazor** as your *Build Presets*. The *App location* needs to be the location of the project file; in our case `src/TinyBlazorAdmin/`. The *App artifact location* can be left to wwwroot.
-
->Note: We don't need the Api location, because AzURLShortener is deployed in a full standalone Azure Function.
-
-Once it's all filled, click the Review, and create button. It will takes a few minutes to get deployed. During this time let's create and configure our security components.
-
-## Create Azure Active Directory (AAD) Components
-
-### Create AAD App for the Fontend
-
-We need a Service Principal that we will use to authenticate our user to Azure Active Directory (AAD). To achieve this we will create an application registration in Azure.
-
-From the Azure Portal (portal.azure.com), open the **Azure Active Directory** page. From the left option menu select **App registrations**, then create a new registration. Use a name that will help you to remember that it's for the TinyBlazorAdmin website (ex: TinyAdminApp) (1)
-
-![RegisterClientApp][RegisterClientApp]
-
-For the Redirect URL use **Web** (3) and enter the URL of the Azure Static WebApp deployed previously and add `/authentication/login-callback`. It should look lsomething like this:
-
-```
-https://bolly-tiger-04a15beef.azurestaticapps.net/authentication/login-callback
-
-```
-
-**Note the ClientID and TenantID.**
-
-If you need to retrieve the ClientID and TenantID, they will be display at the top of the page once you select an app in the portal.
-
-![Create a new registration][newRegistration]
-
-Go back in the Authentication and in the section Implicit grant check the checkbox `Access Token` and `ID Tokens`
-
-![tokensaccess][tokensaccess]
-
-### Create App for the Azure Function
-
-We need a second App registration, this time to let the Azure Function validate that user information contained in the token is valid.
-
-![First steps to create the AD App registration][azFunction_Auth_step1]
-
-From the Azure Portal, go to your Azure Function. From the left panel select the *Authentication / Authorization* (1) option. Enable the *App Service Authentication* (2) and click on *Azure Active Directory*.
-
-![Configuring the App registration][azFunction_Auth_step2]
-
-1. Select Express.
-2. Make sure *Create New AD App* is selected.
-3. Give the AD App a name.
-4. Click Ok.
-5. DON'T FORGET TO CLICK THE SAVE BUTTON
-
-Now, we need to configure the brand-new Ad App registration. Still from the Azure portal open the *Active Directory* blade. Select the *App registration* option from the left menu. Then select the application you just created.
-
-![ConfigAzFuncADapp][ConfigAzFuncADapp]
-
-1. From the left panel click the *Expose an API* option.
-2. Click the *Add a client application*.
-3. Enter the ClientID of the Frontend App (the first one created).
-4. Check the Impersonation checkbox.
-5. Save by clicking *Add application*.
-
-
-
-## Configure Backend and Frontend to Work Together
-
-Now in your GitHub it's time to update the settings. The code needs to know the AD app to use and the Azure Function to call. Update those values inside `TinyBlazorAdmin\wwwroot\appsettings.json`
-
-> The **Endpoint** _must_ ends with a `/`
-
-```json
-{
- "AzureAd": {
- "Authority": "https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
- "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
- "ValidateAuthority": true
- },
- "UrlShortenerSecuredService": {
- "Endpoint": "https://__azFunction_URL__.azurewebsites.net/"
- }
-}
-```
-
-## Enabeling Cross-Origin Resource Sharing (CORS)
-
-
-First we need the url of the caller (aka TinyBlazorAdmin). From the Azure Portal, open the TinyBlazorAdmin SWA blade. Note the URL display in the top right of the page this is the URL of your admin page.
-
-![swa_URL][swa_URL]
-
-Now we need to add this URL to the list in the CORS of the Azure Function that run AzUrlShortener. From the Azure portal open the blade of AzUrlShortener. From the left menu search CORS, and click it. Add the URL of the SWA and don't forget to save.
-
-
-![azFunction_CORS][azFunction_CORS]
-
-## Try it!
-
-Voila, the deployment is now completed. You can test it by creating a new short URL using the admin SWA.
-
-
-## Adding Custom Domain
-
-To add a custom domain to your AzUrlShortener & TinyBlazorAdmin, [follow these steps](https://github.com/FBoucher/AzUrlShortener/blob/main/post-deployment-configuration.md#add-a-custom-domain) from the the AzUrlShortener repo.
-
-
-[CreateBackend]: medias/CreateBackend.png
-[newRegistration]: medias/newRegistration.png
-[AddPolicy]: medias/AddPolicy.png
-[EditKeyVault]: medias/EditKeyVault.png
-[CreateSecrets]: medias/CreateSecrets.png
-[azFunction_Auth_step1]: medias/azFunction_Auth_step1.png
-[azFunction_Auth_step2]: medias/azFunction_Auth_step2.png
-[ConfigAzFuncADapp]: medias/ConfigAzFuncADapp.png
-[tokensaccess]: medias/tokensaccess.png
-[swa_create1]: medias/swa_create1.png
-[swa_create2]: medias/swa_create2.png
-[swa_create3]: medias/swa_create3.png
-[swa_URL]: medias/swa_URL.png
-[azFunction_CORS]: medias/azFunction_CORS.png
-[RegisterClientApp]: medias/RegisterClientApp.png
-
+# Deployment
+
+Until a "full automatic" deployment is created, here are all the steps to deploy the TinyBlazorAdmin app into Azure. You can run it somewhere else and even locally.
+
+## First thing first
+
+You need to **fork this repo** into your own account. You will need to update the configuration (this document will explain when and what), therefore it needs to be yours.
+
+To fork a GitHub repository click on the fork button on the top right of the screen. If you need more detail have a look to this GitHub doc: [Fork a repo
+](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo).
+
+## Deploy AzUrlShortener (the Backend)
+
+This project is a frontend only so you will need to deploy the [Azure Url Shortener](https://github.com/FBoucher/AzUrlShortener) in "headless mode". Do to it, click the blue button below and make sure to select **none** as Frontend
+
+[![Deploy Backend to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/?WT.mc_id=urlshortener-github-frbouche#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FFBoucher%2FAzUrlShortener%2Fmain%2Fdeployment%2FazureDeploy.json)
+
+![CreateBackend][CreateBackend]
+
+
+## Deploy TinyBlazorAdmin (the Frontend)
+
+There are many ways you could run Tiny Blazor Admin website. In this deployment, we will use the new [Azure Static Web App (SWA)](https://azure.microsoft.com/en-ca/services/app-service/static/?WT.mc_id=tinyblazoradmin-github-frbouche). However, because the TinyBlazorAdmin use Azure Active Directory (AAD), we need a standalone Azure Function (deployed at the previous step).
+
+Open Azure portal (portal.azure.com), open the **resource group** where you created the backend (ex: streamDemo is our case). Click the "**+**" and search **Static Web App**, and click the *Create* button.
+
+![Creating swa][swa_create1]
+
+> Note: You will need to **Authorize Azure Static Web Apps**, to have access to _your_ GitHub repository (the one created when you forked the project). This is required because SWA uses GitHub Action to deploy.
+
+![Creating swa part 2][swa_create2]
+
+Select your organization, repository and branch (ex: main).
+
+![Creating swa part 3][swa_create3]
+
+Select **Blazor** as your *Build Presets*. The *App location* needs to be the location of the project file; in our case `src/TinyBlazorAdmin/`. The *App artifact location* can be left to wwwroot.
+
+>Note: We don't need the Api location, because AzURLShortener is deployed in a full standalone Azure Function.
+
+Once it's all filled, click the Review, and create button. It will takes a few minutes to get deployed. During this time let's create and configure our security components.
+
+## Create Azure Active Directory (AAD) Components
+
+### Create AAD App for the Fontend
+
+We need a Service Principal that we will use to authenticate our user to Azure Active Directory (AAD). To achieve this we will create an application registration in Azure.
+
+From the Azure Portal (portal.azure.com), open the **Azure Active Directory** page. From the left option menu select **App registrations**, then create a new registration. Use a name that will help you to remember that it's for the TinyBlazorAdmin website (ex: TinyAdminApp) (1)
+
+![RegisterClientApp][RegisterClientApp]
+
+For the Redirect URL use **Web** (3) and enter the URL of the Azure Static WebApp deployed previously and add `/authentication/login-callback`. It should look lsomething like this:
+
+```
+https://bolly-tiger-04a15beef.azurestaticapps.net/authentication/login-callback
+
+```
+
+**Note the ClientID and TenantID.**
+
+If you need to retrieve the ClientID and TenantID, they will be display at the top of the page once you select an app in the portal.
+
+![Create a new registration][newRegistration]
+
+Go back in the Authentication and in the section Implicit grant check the checkbox `Access Token` and `ID Tokens`
+
+![tokensaccess][tokensaccess]
+
+### Create App for the Azure Function
+
+We need a second App registration, this time to let the Azure Function validate that user information contained in the token is valid.
+
+![First steps to create the AD App registration][azFunction_Auth_step1]
+
+From the Azure Portal, go to your Azure Function. From the left panel select the *Authentication / Authorization* (1) option. Enable the *App Service Authentication* (2) and click on *Azure Active Directory*.
+
+![Configuring the App registration][azFunction_Auth_step2]
+
+1. Select Express.
+2. Make sure *Create New AD App* is selected.
+3. Give the AD App a name.
+4. Click Ok.
+5. DON'T FORGET TO CLICK THE SAVE BUTTON
+
+Now, we need to configure the brand-new Ad App registration. Still from the Azure portal open the *Active Directory* blade. Select the *App registration* option from the left menu. Then select the application you just created.
+
+![ConfigAzFuncADapp][ConfigAzFuncADapp]
+
+1. From the left panel click the *Expose an API* option.
+2. Click the *Add a client application*.
+3. Enter the ClientID of the Frontend App (the first one created).
+4. Check the Impersonation checkbox.
+5. Save by clicking *Add application*.
+
+
+
+## Configure Backend and Frontend to Work Together
+
+Now in your GitHub it's time to update the settings. The code needs to know the AD app to use and the Azure Function to call. Update those values inside `TinyBlazorAdmin\wwwroot\appsettings.json`
+
+> The **Endpoint** _must_ ends with a `/`
+
+```json
+{
+ "AzureAd": {
+ "Authority": "https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
+ "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
+ "ValidateAuthority": true
+ },
+ "UrlShortenerSecuredService": {
+ "Endpoint": "https://__azFunction_URL__.azurewebsites.net/"
+ }
+}
+```
+
+## Enabeling Cross-Origin Resource Sharing (CORS)
+
+
+First we need the url of the caller (aka TinyBlazorAdmin). From the Azure Portal, open the TinyBlazorAdmin SWA blade. Note the URL display in the top right of the page this is the URL of your admin page.
+
+![swa_URL][swa_URL]
+
+Now we need to add this URL to the list in the CORS of the Azure Function that run AzUrlShortener. From the Azure portal open the blade of AzUrlShortener. From the left menu search CORS, and click it. Add the URL of the SWA and don't forget to save.
+
+
+![azFunction_CORS][azFunction_CORS]
+
+## Try it!
+
+Voila, the deployment is now completed. You can test it by creating a new short URL using the admin SWA.
+
+
+## Adding Custom Domain
+
+To add a custom domain to your AzUrlShortener & TinyBlazorAdmin, [follow these steps](https://github.com/FBoucher/AzUrlShortener/blob/main/post-deployment-configuration.md#add-a-custom-domain) from the the AzUrlShortener repo.
+
+
+[CreateBackend]: medias/CreateBackend.png
+[newRegistration]: medias/newRegistration.png
+[AddPolicy]: medias/AddPolicy.png
+[EditKeyVault]: medias/EditKeyVault.png
+[CreateSecrets]: medias/CreateSecrets.png
+[azFunction_Auth_step1]: medias/azFunction_Auth_step1.png
+[azFunction_Auth_step2]: medias/azFunction_Auth_step2.png
+[ConfigAzFuncADapp]: medias/ConfigAzFuncADapp.png
+[tokensaccess]: medias/tokensaccess.png
+[swa_create1]: medias/swa_create1.png
+[swa_create2]: medias/swa_create2.png
+[swa_create3]: medias/swa_create3.png
+[swa_URL]: medias/swa_URL.png
+[azFunction_CORS]: medias/azFunction_CORS.png
+[RegisterClientApp]: medias/RegisterClientApp.png
+
\ No newline at end of file
diff --git a/src/TinyBlazorAdmin.sln b/src/TinyBlazorAdmin.sln
index 358919b..2a2e379 100644
--- a/src/TinyBlazorAdmin.sln
+++ b/src/TinyBlazorAdmin.sln
@@ -1,34 +1,34 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30114.105
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyBlazorAdmin", "TinyBlazorAdmin\TinyBlazorAdmin.csproj", "{2190B73D-DF38-45D8-88C1-6CDF49E68D68}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdminApi", "AdminApi\AdminApi.csproj", "{3663F7F4-2064-423E-9928-3F1D25E3F2D7}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azShortenerLib", "azShortenerLib\azShortenerLib.csproj", "{3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}"
-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
- {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Release|Any CPU.Build.0 = Release|Any CPU
- {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Release|Any CPU.Build.0 = Release|Any CPU
- {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyBlazorAdmin", "client\TinyBlazorAdmin.csproj", "{2190B73D-DF38-45D8-88C1-6CDF49E68D68}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdminApi", "api\AdminApi.csproj", "{3663F7F4-2064-423E-9928-3F1D25E3F2D7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "azShortenerLib", "lib\azShortenerLib.csproj", "{3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}"
+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
+ {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2190B73D-DF38-45D8-88C1-6CDF49E68D68}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3663F7F4-2064-423E-9928-3F1D25E3F2D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E0E3C70-A56F-4565-ADE2-E3E585D1CBFD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/TinyBlazorAdmin/Shared/LoginDisplay.razor b/src/TinyBlazorAdmin/Shared/LoginDisplay.razor
deleted file mode 100644
index 7ebea5d..0000000
--- a/src/TinyBlazorAdmin/Shared/LoginDisplay.razor
+++ /dev/null
@@ -1,23 +0,0 @@
-@using Microsoft.AspNetCore.Components.Authorization
-@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
-
-@inject NavigationManager Navigation
-@inject SignOutSessionStateManager SignOutManager
-
-
-
- Hello, @context.User.Identity?.Name!
-
-
-
- Log in
-
-
-
-@code{
- private async Task BeginLogout(MouseEventArgs args)
- {
- await SignOutManager.SetSignOutState();
- Navigation.NavigateTo("authentication/logout");
- }
-}
diff --git a/src/TinyBlazorAdmin/Shared/MainLayout.razor b/src/TinyBlazorAdmin/Shared/MainLayout.razor
deleted file mode 100644
index 43b4c3c..0000000
--- a/src/TinyBlazorAdmin/Shared/MainLayout.razor
+++ /dev/null
@@ -1,18 +0,0 @@
-@inherits LayoutComponentBase
-
-
-
- @Title
-
-
- Please take our
- brief survey
-
- and tell us what you think.
-
-
-@code {
- // Demonstrates how a parent component can supply parameters
- [Parameter]
- public string? Title { get; set; }
-}
diff --git a/src/TinyBlazorAdmin/staticwebapp.config.json b/src/TinyBlazorAdmin/staticwebapp.config.json
deleted file mode 100644
index 4a1bb1f..0000000
--- a/src/TinyBlazorAdmin/staticwebapp.config.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "navigationFallback": {
- "rewrite": "/index.html"
- }
-}
\ No newline at end of file
diff --git a/src/TinyBlazorAdmin/wwwroot/appsettings.json b/src/TinyBlazorAdmin/wwwroot/appsettings.json
deleted file mode 100644
index 08a67f3..0000000
--- a/src/TinyBlazorAdmin/wwwroot/appsettings.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/TinyBlazorAdmin/wwwroot/favicon.ico b/src/TinyBlazorAdmin/wwwroot/favicon.ico
deleted file mode 100644
index 63e859b..0000000
Binary files a/src/TinyBlazorAdmin/wwwroot/favicon.ico and /dev/null differ
diff --git a/src/TinyBlazorAdmin/wwwroot/icon-192.png b/src/TinyBlazorAdmin/wwwroot/icon-192.png
deleted file mode 100644
index 166f56d..0000000
Binary files a/src/TinyBlazorAdmin/wwwroot/icon-192.png and /dev/null differ
diff --git a/src/AdminApi/.gitignore b/src/api/.gitignore
similarity index 100%
rename from src/AdminApi/.gitignore
rename to src/api/.gitignore
diff --git a/src/AdminApi/AdminApi.csproj b/src/api/AdminApi.csproj
similarity index 92%
rename from src/AdminApi/AdminApi.csproj
rename to src/api/AdminApi.csproj
index 18a597e..17efde6 100644
--- a/src/AdminApi/AdminApi.csproj
+++ b/src/api/AdminApi.csproj
@@ -20,6 +20,6 @@
-
+
diff --git a/src/AdminApi/Program.cs b/src/api/Program.cs
similarity index 97%
rename from src/AdminApi/Program.cs
rename to src/api/Program.cs
index f39d3d3..699c96d 100644
--- a/src/AdminApi/Program.cs
+++ b/src/api/Program.cs
@@ -1,37 +1,37 @@
-using System.Threading.Tasks;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Azure.Functions.Worker.Configuration;
-using Cloud5mins.domain;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Cloud5mins.AdminApi
-{
- public class Program
- {
- public static void Main()
- {
- AdminApiSettings AdminApiSettings = null;
-
- var host = new HostBuilder()
- .ConfigureFunctionsWorkerDefaults()
- .ConfigureServices((context, services) =>
- {
- // Add our global configuration instance
- services.AddSingleton(options =>
- {
- var configuration = context.Configuration;
- AdminApiSettings = new AdminApiSettings();
- configuration.Bind(AdminApiSettings);
- return configuration;
- });
-
- // Add our configuration class
- services.AddSingleton(options => { return AdminApiSettings; });
- })
- .Build();
-
- host.Run();
- }
- }
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Azure.Functions.Worker.Configuration;
+using Cloud5mins.domain;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Cloud5mins.AdminApi
+{
+ public class Program
+ {
+ public static void Main()
+ {
+ AdminApiSettings AdminApiSettings = null;
+
+ var host = new HostBuilder()
+ .ConfigureFunctionsWorkerDefaults()
+ .ConfigureServices((context, services) =>
+ {
+ // Add our global configuration instance
+ services.AddSingleton(options =>
+ {
+ var configuration = context.Configuration;
+ AdminApiSettings = new AdminApiSettings();
+ configuration.Bind(AdminApiSettings);
+ return configuration;
+ });
+
+ // Add our configuration class
+ services.AddSingleton(options => { return AdminApiSettings; });
+ })
+ .Build();
+
+ host.Run();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/AdminApi/domain/AdminApiSettings.cs b/src/api/domain/AdminApiSettings.cs
similarity index 100%
rename from src/AdminApi/domain/AdminApiSettings.cs
rename to src/api/domain/AdminApiSettings.cs
diff --git a/src/api/domain/ClaimsUtility.cs b/src/api/domain/ClaimsUtility.cs
new file mode 100644
index 0000000..6137529
--- /dev/null
+++ b/src/api/domain/ClaimsUtility.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Security.Claims;
+using System.Text.Json;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Cloud5mins.domain
+{
+ public static class ClaimsUtility
+ {
+
+ public static HttpStatusCode CatchUnauthorize(HttpRequestData req, ILogger log)
+ {
+ try{
+
+ ClaimsPrincipal principal = StaticWebAppsAuth.GetClaimsPrincipal(req,log);
+
+ if (principal == null)
+ {
+ log.LogTrace("No principal.");
+ return HttpStatusCode.Unauthorized;
+ }
+
+ if(!principal.IsInRole("admin"))
+ {
+ log.LogInformation("Not IsInRole admin");
+ var claims = new List( principal.FindAll(ClaimTypes.Role));
+ foreach(var c in claims){
+ if(c.Value == "admin")
+ return HttpStatusCode.Continue;
+ }
+ log.LogInformation("No claim with value admin");
+ return HttpStatusCode.Unauthorized;
+ }
+
+ return HttpStatusCode.Continue;
+ }
+ catch (Exception ex)
+ {
+ log.LogError(ex, "An unexpected error was encountered.");
+ return HttpStatusCode.BadRequest;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/api/domain/StaticWebAppsAuth.cs b/src/api/domain/StaticWebAppsAuth.cs
new file mode 100644
index 0000000..8f12eeb
--- /dev/null
+++ b/src/api/domain/StaticWebAppsAuth.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Json;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Cloud5mins.domain
+{
+ public static class StaticWebAppsAuth
+ {
+ private class ClientPrincipal
+ {
+ public string IdentityProvider { get; set; }
+ public string UserId { get; set; }
+ public string UserDetails { get; set; }
+ public IEnumerable UserRoles { get; set; }
+ }
+
+ public static ClaimsPrincipal GetClaimsPrincipal(HttpRequestData req, ILogger log)
+ {
+ var principal = new ClientPrincipal();
+
+ if (req.Headers.TryGetValues("x-ms-client-principal", out var header))
+ {
+ var data = header.First();
+ var decoded = Convert.FromBase64String(data);
+ var json = Encoding.UTF8.GetString(decoded);
+ log.LogWarning($"===> json: {json}");
+ principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ }
+
+ principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
+
+ if (!principal.UserRoles?.Any() ?? true)
+ {
+ return new ClaimsPrincipal();
+ }
+
+ var identity = new ClaimsIdentity(principal.IdentityProvider);
+ identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId));
+ identity.AddClaim(new Claim(ClaimTypes.Name, principal.UserDetails));
+ identity.AddClaims(principal.UserRoles.Select(r => new Claim(ClaimTypes.Role, r)));
+
+ return new ClaimsPrincipal(identity);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AdminApi/function/UrlArchive.cs b/src/api/function/UrlArchive.cs
similarity index 85%
rename from src/AdminApi/function/UrlArchive.cs
rename to src/api/function/UrlArchive.cs
index c684623..f448001 100644
--- a/src/AdminApi/function/UrlArchive.cs
+++ b/src/api/function/UrlArchive.cs
@@ -1,113 +1,108 @@
-/*
-```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 System;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using System.Net;
-using Microsoft.Extensions.Configuration;
-using System.Security.Claims;
-using Microsoft.AspNetCore.Mvc;
-using System.IO;
-using System.Text.Json;
-using Microsoft.Azure.Functions.Worker;
-using Microsoft.Azure.Functions.Worker.Http;
-using Cloud5mins.domain;
-using System.Threading;
-using Cloud5mins.AzShortener;
-
-namespace Cloud5mins.Function
-{
- public class UrlArchive
- {
-
- private readonly ILogger _logger;
- private readonly AdminApiSettings _adminApiSettings;
-
- public UrlArchive(ILoggerFactory loggerFactory, AdminApiSettings settings)
- {
- _logger = loggerFactory.CreateLogger();
- _adminApiSettings = settings;
- }
-
- [Function("UrlArchive")]
- public async Task Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
- ExecutionContext context)
- {
- _logger.LogInformation($"C# HTTP trigger function processed this request: {req}");
-
- string userId = string.Empty;
- ShortUrlEntity input;
- ShortUrlEntity result;
- try
- {
- // var invalidRequest = Utility.CatchUnauthorize(principal, log);
- // if (invalidRequest != null)
- // {
- // return invalidRequest;
- // }
- // else
- // {
- // userId = principal.FindFirst(ClaimTypes.GivenName).Value;
- // _logger.LogInformation("Authenticated user {user}.", userId);
- // }
-
- // Validation of the inputs
- if (req == null)
- {
- return req.CreateResponse( HttpStatusCode.NotFound);
- }
-
- using (var reader = new StreamReader(req.Body))
- {
- var body = reader.ReadToEnd();
- input = JsonSerializer.Deserialize(body, new JsonSerializerOptions {PropertyNameCaseInsensitive = true});
- if (input == null)
- {
- return req.CreateResponse( HttpStatusCode.NotFound);
- }
- }
-
- StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
-
- 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 { Message = ex.Message} );
- return badRequest;
- }
-
- var response = req.CreateResponse(HttpStatusCode.OK);
- await response.WriteAsJsonAsync(result);
- return response;
- }
- }
-}
+/*
+```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 System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using Microsoft.Extensions.Configuration;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+using System.IO;
+using System.Text.Json;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Cloud5mins.domain;
+using System.Threading;
+using Cloud5mins.AzShortener;
+
+namespace Cloud5mins.Function
+{
+ public class UrlArchive
+ {
+
+ private readonly ILogger _logger;
+ private readonly AdminApiSettings _adminApiSettings;
+
+ public UrlArchive(ILoggerFactory loggerFactory, AdminApiSettings settings)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _adminApiSettings = settings;
+ }
+
+ [Function("UrlArchive")]
+ public async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
+ ExecutionContext context)
+ {
+ _logger.LogInformation($"HTTP trigger - UrlArchive");
+
+ string userId = string.Empty;
+ ShortUrlEntity input;
+ ShortUrlEntity result;
+ try
+ {
+ var invalidCode = ClaimsUtility.CatchUnauthorize(req, _logger);
+ if (invalidCode != HttpStatusCode.Continue)
+ {
+ return req.CreateResponse(invalidCode);
+ }
+
+ // Validation of the inputs
+ if (req == null)
+ {
+ return req.CreateResponse( HttpStatusCode.NotFound);
+ }
+
+ using (var reader = new StreamReader(req.Body))
+ {
+ var body = reader.ReadToEnd();
+ input = JsonSerializer.Deserialize(body, new JsonSerializerOptions {PropertyNameCaseInsensitive = true});
+ if (input == null)
+ {
+ return req.CreateResponse( HttpStatusCode.NotFound);
+ }
+ }
+
+ StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
+
+ 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 { Message = ex.Message} );
+ return badRequest;
+ }
+
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(result);
+ return response;
+ }
+ }
+}
diff --git a/src/AdminApi/function/UrlClickStatsByDay.cs b/src/api/function/UrlClickStatsByDay.cs
similarity index 87%
rename from src/AdminApi/function/UrlClickStatsByDay.cs
rename to src/api/function/UrlClickStatsByDay.cs
index 2c51862..6bcb4f1 100644
--- a/src/AdminApi/function/UrlClickStatsByDay.cs
+++ b/src/api/function/UrlClickStatsByDay.cs
@@ -1,118 +1,113 @@
-/*
-```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 System;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using System.Net;
-using Cloud5mins.domain;
-using System.Security.Claims;
-using System.Text.Json;
-using System.IO;
-using System.Linq;
-using Microsoft.Azure.Functions.Worker;
-using Microsoft.Azure.Functions.Worker.Http;
-using System.Threading;
-using Cloud5mins.AzShortener;
-
-namespace Cloud5mins.Function
-{
- public class UrlClickStatsByDay
- {
- private readonly ILogger _logger;
- private readonly AdminApiSettings _adminApiSettings;
-
- public UrlClickStatsByDay(ILoggerFactory loggerFactory, AdminApiSettings settings)
- {
- _logger = loggerFactory.CreateLogger();
- _adminApiSettings = settings;
- }
-
- [Function("UrlClickStatsByDay")]
- public async Task Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
- ExecutionContext context)
- {
- _logger.LogInformation($"C# HTTP trigger function processed this request: {req}");
-
- string userId = string.Empty;
- UrlClickStatsRequest input;
- var result = new ClickDateList();
-
- // var invalidRequest = Utility.CatchUnauthorize(principal, log);
- // if (invalidRequest != null)
- // {
- // return invalidRequest;
- // }
- // else
- // {
- // userId = principal.FindFirst(ClaimTypes.NameIdentifier).Value;
- // _logger.LogInformation("Authenticated user {user}.", userId);
- // }
-
- // Validation of the inputs
- if (req == null)
- {
- return req.CreateResponse( HttpStatusCode.NotFound);
- }
-
- try
- {
- using (var reader = new StreamReader(req.Body))
- {
- var strBody = reader.ReadToEnd();
- input = JsonSerializer.Deserialize(strBody, new JsonSerializerOptions {PropertyNameCaseInsensitive = true});
- if (input == null)
- {
- return req.CreateResponse( HttpStatusCode.NotFound);
- }
- }
-
- StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
-
- 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(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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;
- }
- }
-}
+/*
+```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 System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using Cloud5mins.domain;
+using System.Security.Claims;
+using System.Text.Json;
+using System.IO;
+using System.Linq;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using System.Threading;
+using Cloud5mins.AzShortener;
+
+namespace Cloud5mins.Function
+{
+ public class UrlClickStatsByDay
+ {
+ private readonly ILogger _logger;
+ private readonly AdminApiSettings _adminApiSettings;
+
+ public UrlClickStatsByDay(ILoggerFactory loggerFactory, AdminApiSettings settings)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _adminApiSettings = settings;
+ }
+
+ [Function("UrlClickStatsByDay")]
+ public async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
+ ExecutionContext context)
+ {
+ _logger.LogInformation($"HTTP trigger: UrlClickStatsByDay");
+
+ string userId = string.Empty;
+ UrlClickStatsRequest input;
+ var result = new ClickDateList();
+
+ var invalidCode = ClaimsUtility.CatchUnauthorize(req, _logger);
+ if (invalidCode != HttpStatusCode.Continue)
+ {
+ return req.CreateResponse(invalidCode);
+ }
+
+ // Validation of the inputs
+ if (req == null)
+ {
+ return req.CreateResponse( HttpStatusCode.NotFound);
+ }
+
+ try
+ {
+ using (var reader = new StreamReader(req.Body))
+ {
+ var strBody = reader.ReadToEnd();
+ input = JsonSerializer.Deserialize(strBody, new JsonSerializerOptions {PropertyNameCaseInsensitive = true});
+ if (input == null)
+ {
+ return req.CreateResponse( HttpStatusCode.NotFound);
+ }
+ }
+
+ StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
+
+ 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(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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/AdminApi/function/UrlList.cs b/src/api/function/UrlList.cs
similarity index 79%
rename from src/AdminApi/function/UrlList.cs
rename to src/api/function/UrlList.cs
index 5486634..cad1e44 100644
--- a/src/AdminApi/function/UrlList.cs
+++ b/src/api/function/UrlList.cs
@@ -1,95 +1,84 @@
-/*
-```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 System;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Azure.Functions.Worker;
-using Microsoft.Azure.Functions.Worker.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System.Linq;
-
-using Cloud5mins.AzShortener;
-using Cloud5mins.domain;
-
-
-//using Microsoft.AspNetCore.Http;
-
-namespace Cloud5mins.Function
-{
- public class UrlList
- {
-
- private readonly ILogger _logger;
- private readonly AdminApiSettings _adminApiSettings;
-
- public UrlList(ILoggerFactory loggerFactory, AdminApiSettings settings)
- {
- _logger = loggerFactory.CreateLogger();
- _adminApiSettings = settings;
- }
-
- [Function("UrlList")]
- public async Task Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestData req, ExecutionContext context)
- {
- _logger.LogInformation($"C# HTTP trigger function processed this request: {req}");
-
- var result = new ListResponse();
- string userId = string.Empty;
-
-
- StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
-
- try
- {
- // var invalidRequest = Utility.CatchUnauthorize(principal, log);
- // if (invalidRequest != null)
- // {
- // return invalidRequest;
- // }
- // else
- // {
- // userId = principal.FindFirst(ClaimTypes.GivenName).Value;
- // _logger.LogInformation("Authenticated user {user}.", userId);
- // }
-
- result.UrlList = await stgHelper.GetAllShortUrlEntities();
- result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList();
- var host = string.IsNullOrEmpty(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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 {Message = ex.Message });
- return badres;
- }
-
- var response = req.CreateResponse(HttpStatusCode.OK);
- await response.WriteAsJsonAsync(result);
-
- return response;
- }
- }
-}
+/*
+```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 System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+
+using Cloud5mins.AzShortener;
+using Cloud5mins.domain;
+
+namespace Cloud5mins.Function
+{
+ public class UrlList
+ {
+
+ private readonly ILogger _logger;
+ private readonly AdminApiSettings _adminApiSettings;
+
+ public UrlList(ILoggerFactory loggerFactory, AdminApiSettings settings)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _adminApiSettings = settings;
+ }
+
+ [Function("UrlList")]
+ public async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestData req, ExecutionContext context)
+ {
+ _logger.LogInformation($"Starting UrlList...");
+
+ var result = new ListResponse();
+ string userId = string.Empty;
+
+ StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
+
+ try
+ {
+ var invalidCode = ClaimsUtility.CatchUnauthorize(req, _logger);
+ if (invalidCode != HttpStatusCode.Continue)
+ {
+ return req.CreateResponse(invalidCode);
+ }
+
+ result.UrlList = await stgHelper.GetAllShortUrlEntities();
+ result.UrlList = result.UrlList.Where(p => !(p.IsArchived ?? false)).ToList();
+ var host = string.IsNullOrEmpty(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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 {Message = ex.Message });
+ return badres;
+ }
+
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(result);
+ return response;
+ }
+ }
+}
diff --git a/src/AdminApi/function/UrlShortener.cs b/src/api/function/UrlShortener.cs
similarity index 90%
rename from src/AdminApi/function/UrlShortener.cs
rename to src/api/function/UrlShortener.cs
index efaace4..0edaa48 100644
--- a/src/AdminApi/function/UrlShortener.cs
+++ b/src/api/function/UrlShortener.cs
@@ -1,157 +1,147 @@
-/*
-```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 System;
-using System.Threading.Tasks;
-using Microsoft.Azure.Functions.Worker;
-using Microsoft.Azure.Functions.Worker.Http;
-using Microsoft.Extensions.Logging;
-using System.Net;
-// using Microsoft.Extensions.Configuration;
-// using System.Security.Claims;
-// using Microsoft.AspNetCore.Mvc;
-// using Microsoft.AspNetCore.Http;
-using System.IO;
-using System.Text.Json;
-using System.Threading;
-
-using Cloud5mins.domain;
-using Cloud5mins.AzShortener;
-
-namespace Cloud5mins.Function
-{
-
- public class UrlShortener
- {
- private readonly ILogger _logger;
- private readonly AdminApiSettings _adminApiSettings;
-
- public UrlShortener(ILoggerFactory loggerFactory, AdminApiSettings settings)
- {
- _logger = loggerFactory.CreateLogger();
- _adminApiSettings = settings;
- }
-
- [Function("UrlShortener")]
- public async Task Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
- ExecutionContext context
- )
- {
- _logger.LogInformation($"__trace creating shortURL: {req}");
- string userId = string.Empty;
- ShortRequest input;
- var result = new ShortResponse();
-
- try
- {
- // var invalidRequest = Utility.CatchUnauthorize(principal, log);
-
- // if (invalidRequest != null)
- // {
- // return invalidRequest;
- // }
- // else
- // {
- // userId = principal.FindFirst(ClaimTypes.GivenName).Value;
- // _logger.LogInformation("Authenticated user {user}.", userId);
- // }
-
- // Validation of the inputs
- if (req == null)
- {
- return req.CreateResponse(HttpStatusCode.NotFound);
- }
-
- using (var reader = new StreamReader(req.Body))
- {
- var strBody = reader.ReadToEnd();
- 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(_adminApiSettings.UlsDataStorage);
-
- 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(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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 {Message = ex.Message});
- return badResponse;
- }
-
- var response = req.CreateResponse(HttpStatusCode.OK);
- await response.WriteAsJsonAsync(result);
-
- return response;
- }
- }
-}
+/*
+```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 System;
+using System.Threading.Tasks;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+
+using Cloud5mins.domain;
+using Cloud5mins.AzShortener;
+
+namespace Cloud5mins.Function
+{
+
+ public class UrlShortener
+ {
+ private readonly ILogger _logger;
+ private readonly AdminApiSettings _adminApiSettings;
+
+ public UrlShortener(ILoggerFactory loggerFactory, AdminApiSettings settings)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _adminApiSettings = settings;
+ }
+
+ [Function("UrlShortener")]
+ public async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
+ ExecutionContext context
+ )
+ {
+ _logger.LogInformation($"__trace creating shortURL: {req}");
+ string userId = string.Empty;
+ ShortRequest input;
+ var result = new ShortResponse();
+
+ try
+ {
+ var invalidCode = ClaimsUtility.CatchUnauthorize(req, _logger);
+ if (invalidCode != HttpStatusCode.Continue)
+ {
+ return req.CreateResponse(invalidCode);
+ }
+
+ // Validation of the inputs
+ if (req == null)
+ {
+ return req.CreateResponse(HttpStatusCode.NotFound);
+ }
+
+ using (var reader = new StreamReader(req.Body))
+ {
+ var strBody = reader.ReadToEnd();
+ 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(_adminApiSettings.UlsDataStorage);
+
+ 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(_adminApiSettings.customDomain) ? req.Url.Host: _adminApiSettings.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 {Message = ex.Message});
+ return badResponse;
+ }
+
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(result);
+
+ return response;
+ }
+ }
+}
diff --git a/src/AdminApi/function/UrlUpdate.cs b/src/api/function/UrlUpdate.cs
similarity index 84%
rename from src/AdminApi/function/UrlUpdate.cs
rename to src/api/function/UrlUpdate.cs
index 27c09fc..fad4387 100644
--- a/src/AdminApi/function/UrlUpdate.cs
+++ b/src/api/function/UrlUpdate.cs
@@ -1,149 +1,136 @@
-/*
-```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 System;
-using System.Threading.Tasks;
-// 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.Net;
-using System.Net.Http;
-using Cloud5mins.domain;
-using Cloud5mins.AzShortener;
-using Microsoft.Extensions.Configuration;
-using System.Security.Claims;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Http;
-using System.IO;
-using System.Text.Json;
-using System.Threading;
-
-namespace Cloud5mins.Function
-{
- public class UrlUpdate
- {
- private readonly ILogger _logger;
- private readonly AdminApiSettings _adminApiSettings;
-
- public UrlUpdate(ILoggerFactory loggerFactory, AdminApiSettings settings)
- {
- _logger = loggerFactory.CreateLogger();
- _adminApiSettings = settings;
- }
-
-
- [Function("UrlUpdate")]
- public async Task Run(
- [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
- ExecutionContext context
- )
- {
- _logger.LogInformation($"C# HTTP trigger function processed this request: {req}");
-
- string userId = string.Empty;
- ShortUrlEntity input;
- ShortUrlEntity result;
-
- try
- {
- // var invalidRequest = Utility.CatchUnauthorize(principal, _logger);
-
- // if (invalidRequest != null)
- // {
- // return invalidRequest;
- // }
- // else
- // {
- // userId = principal.FindFirst(ClaimTypes.GivenName).Value;
- // _logger.LogInformation("Authenticated user {user}.", userId);
- // }
-
- // Validation of the inputs
- if (req == null)
- {
- return req.CreateResponse( HttpStatusCode.NotFound);
- }
-
- using (var reader = new StreamReader(req.Body))
- {
- var strBody = reader.ReadToEnd();
- 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;
- }
-
- // var config = new ConfigurationBuilder()
- // .SetBasePath(context.FunctionAppDirectory)
- // .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
- // .AddEnvironmentVariables()
- // .Build();
-
- StorageTableHelper stgHelper = new StorageTableHelper(_adminApiSettings.UlsDataStorage);
-
- result = await stgHelper.UpdateShortUrlEntity(input);
- var host = string.IsNullOrEmpty(_adminApiSettings.customDomain) ? req.Url.Host : _adminApiSettings.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 { Message = ex.Message} );
- return badRequest;
- }
-
- var response = req.CreateResponse(HttpStatusCode.OK);
- await response.WriteAsJsonAsync(result);
- return response;
- }
- }
+/*
+```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 System;
+using System.Threading.Tasks;
+// 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.Net;
+using System.Net.Http;
+using Cloud5mins.domain;
+using Cloud5mins.AzShortener;
+using Microsoft.Extensions.Configuration;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+
+namespace Cloud5mins.Function
+{
+ public class UrlUpdate
+ {
+ private readonly ILogger _logger;
+ private readonly AdminApiSettings _adminApiSettings;
+
+ public UrlUpdate(ILoggerFactory loggerFactory, AdminApiSettings settings)
+ {
+ _logger = loggerFactory.CreateLogger();
+ _adminApiSettings = settings;
+ }
+
+ [Function("UrlUpdate")]
+ public async Task Run(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req,
+ ExecutionContext context
+ )
+ {
+ _logger.LogInformation($"HTTP trigger - UrlUpdate");
+
+ string userId = string.Empty;
+ ShortUrlEntity input;
+ ShortUrlEntity result;
+
+ try
+ {
+ var invalidCode = ClaimsUtility.CatchUnauthorize(req, _logger);
+ if (invalidCode != HttpStatusCode.Continue)
+ {
+ return req.CreateResponse(invalidCode);
+ }
+
+ // Validation of the inputs
+ if (req == null)
+ {
+ return req.CreateResponse( HttpStatusCode.NotFound);
+ }
+
+ using (var reader = new StreamReader(req.Body))
+ {
+ var strBody = reader.ReadToEnd();
+ 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(_adminApiSettings.UlsDataStorage);
+
+ result = await stgHelper.UpdateShortUrlEntity(input);
+ var host = string.IsNullOrEmpty(_adminApiSettings.customDomain) ? req.Url.Host : _adminApiSettings.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 { Message = 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/AdminApi/host.json b/src/api/host.json
similarity index 100%
rename from src/AdminApi/host.json
rename to src/api/host.json
diff --git a/src/AdminApi/local.settings.example.json b/src/api/local.settings.example.json
similarity index 95%
rename from src/AdminApi/local.settings.example.json
rename to src/api/local.settings.example.json
index a889f86..2c6cbc3 100644
--- a/src/AdminApi/local.settings.example.json
+++ b/src/api/local.settings.example.json
@@ -1,13 +1,13 @@
-{
- "IsEncrypted": false,
- "Values": {
- "AzureWebJobsStorage": "",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
- "UlsDataStorage":"CONNECTIONSTRING_DATA_STORAGE_ACCOUNT"
- },
- "Host": {
- "LocalHttpPort": 7071,
- "CORS": "*",
- "CORSCredentials": false
- }
+{
+ "IsEncrypted": false,
+ "Values": {
+ "AzureWebJobsStorage": "",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
+ "UlsDataStorage":"CONNECTIONSTRING_DATA_STORAGE_ACCOUNT"
+ },
+ "Host": {
+ "LocalHttpPort": 7071,
+ "CORS": "*",
+ "CORSCredentials": false
+ }
}
\ No newline at end of file
diff --git a/src/TinyBlazorAdmin/.vscode/launch.json b/src/client/.vscode/launch.json
similarity index 97%
rename from src/TinyBlazorAdmin/.vscode/launch.json
rename to src/client/.vscode/launch.json
index e8c2dbe..e8d72de 100644
--- a/src/TinyBlazorAdmin/.vscode/launch.json
+++ b/src/client/.vscode/launch.json
@@ -1,14 +1,14 @@
-{
- // 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
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Launch and Debug Standalone Blazor WebAssembly App",
- "type": "blazorwasm",
- "request": "launch",
- "cwd": "${workspaceFolder}"
- }
- ]
+{
+ // 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
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch and Debug Standalone Blazor WebAssembly App",
+ "type": "blazorwasm",
+ "request": "launch",
+ "cwd": "${workspaceFolder}"
+ }
+ ]
}
\ No newline at end of file
diff --git a/src/TinyBlazorAdmin/.vscode/tasks.json b/src/client/.vscode/tasks.json
similarity index 96%
rename from src/TinyBlazorAdmin/.vscode/tasks.json
rename to src/client/.vscode/tasks.json
index 8abc9fd..026ecac 100644
--- a/src/TinyBlazorAdmin/.vscode/tasks.json
+++ b/src/client/.vscode/tasks.json
@@ -1,42 +1,42 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/TinyBlazorAdmin.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "publish",
- "command": "dotnet",
- "type": "process",
- "args": [
- "publish",
- "${workspaceFolder}/TinyBlazorAdmin.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "watch",
- "command": "dotnet",
- "type": "process",
- "args": [
- "watch",
- "run",
- "${workspaceFolder}/TinyBlazorAdmin.csproj",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/TinyBlazorAdmin.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/TinyBlazorAdmin.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "${workspaceFolder}/TinyBlazorAdmin.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
}
\ No newline at end of file
diff --git a/src/TinyBlazorAdmin/App.razor b/src/client/App.razor
similarity index 52%
rename from src/TinyBlazorAdmin/App.razor
rename to src/client/App.razor
index d07039a..51ab545 100644
--- a/src/TinyBlazorAdmin/App.razor
+++ b/src/client/App.razor
@@ -1,25 +1,16 @@
-
-
-
-
-
- @if (context.User.Identity?.IsAuthenticated != true)
- {
-
- }
- else
- {
-
You are not authorized to access this resource.
- }
-
-
-
-
-
- Not found
-
-
Sorry, there's nothing at this address.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Not found
+
+
+
+
diff --git a/src/TinyBlazorAdmin/Pages/Authentication.razor b/src/client/Pages/Authentication.razor
similarity index 96%
rename from src/TinyBlazorAdmin/Pages/Authentication.razor
rename to src/client/Pages/Authentication.razor
index 6c74356..b53dee2 100644
--- a/src/TinyBlazorAdmin/Pages/Authentication.razor
+++ b/src/client/Pages/Authentication.razor
@@ -1,7 +1,7 @@
-@page "/authentication/{action}"
-@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
-
-
-@code{
- [Parameter] public string? Action { get; set; }
-}
+@page "/authentication/{action}"
+@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
+
+
+@code{
+ [Parameter] public string? Action { get; set; }
+}
diff --git a/src/client/Pages/Help.razor b/src/client/Pages/Help.razor
new file mode 100644
index 0000000..af0f049
--- /dev/null
+++ b/src/client/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/TinyBlazorAdmin/Pages/Index.razor b/src/client/Pages/Index.razor
similarity index 98%
rename from src/TinyBlazorAdmin/Pages/Index.razor
rename to src/client/Pages/Index.razor
index a17c522..1e695af 100644
--- a/src/TinyBlazorAdmin/Pages/Index.razor
+++ b/src/client/Pages/Index.razor
@@ -1,20 +1,20 @@
-@page "/"
-
-Tiny Blazor Admin
-
-