diff --git a/ServerlessLibrary.sln b/ServerlessLibrary.sln index a225862..0b225d3 100644 --- a/ServerlessLibrary.sln +++ b/ServerlessLibrary.sln @@ -5,7 +5,9 @@ VisualStudioVersion = 15.0.27428.2037 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessLibraryFunctionApp", "ServerlessLibraryFunctionApp\ServerlessLibraryFunctionApp.csproj", "{95E9DC49-8B38-4D2C-95AD-1BD261A1CBF0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerlessLibraryAPI", "ServerlessLibraryAPI\ServerlessLibraryAPI.csproj", "{9DB5E7FD-1720-4FEC-B6BF-E91BCA659A54}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerlessLibraryAPI", "ServerlessLibraryAPI\ServerlessLibraryAPI.csproj", "{9DB5E7FD-1720-4FEC-B6BF-E91BCA659A54}" +EndProject +Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "ServerlessLibraryLogicApp", "ServerlessLibraryLogicApp\ServerlessLibraryLogicApp.deployproj", "{92DD9A5D-3A93-493C-8F69-23A1C519A1C4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +23,10 @@ Global {9DB5E7FD-1720-4FEC-B6BF-E91BCA659A54}.Debug|Any CPU.Build.0 = Debug|Any CPU {9DB5E7FD-1720-4FEC-B6BF-E91BCA659A54}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DB5E7FD-1720-4FEC-B6BF-E91BCA659A54}.Release|Any CPU.Build.0 = Release|Any CPU + {92DD9A5D-3A93-493C-8F69-23A1C519A1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92DD9A5D-3A93-493C-8F69-23A1C519A1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92DD9A5D-3A93-493C-8F69-23A1C519A1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92DD9A5D-3A93-493C-8F69-23A1C519A1C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ServerlessLibraryAPI/ClientApp/src/actions/actionTypes.js b/ServerlessLibraryAPI/ClientApp/src/actions/actionTypes.js index 2251823..09730c2 100644 --- a/ServerlessLibraryAPI/ClientApp/src/actions/actionTypes.js +++ b/ServerlessLibraryAPI/ClientApp/src/actions/actionTypes.js @@ -1,6 +1,6 @@ export const sampleActionTypes = { GETSAMPLES_SUCCESS: "GETSAMPLES_SUCCESS", - ADDSAMPLE_SUCCESS: "ADDSAMPLE_SUCCESS" + SAMPLESUBMITTED_SUCCESS: "SAMPLESUBMITTED_SUCCESS" }; export const userActionTypes = { diff --git a/ServerlessLibraryAPI/ClientApp/src/actions/sampleActions.js b/ServerlessLibraryAPI/ClientApp/src/actions/sampleActions.js index e7129ee..ebf61b0 100644 --- a/ServerlessLibraryAPI/ClientApp/src/actions/sampleActions.js +++ b/ServerlessLibraryAPI/ClientApp/src/actions/sampleActions.js @@ -2,7 +2,7 @@ import { sampleActionTypes } from "./actionTypes"; export const sampleActions = { getSamplesSuccess, - addSampleSuccess + sampleSubmittedSuccess }; function getSamplesSuccess(samples) { @@ -12,9 +12,9 @@ function getSamplesSuccess(samples) { }; } -function addSampleSuccess(sample) { +function sampleSubmittedSuccess(sample) { return { - type: sampleActionTypes.ADDSAMPLE_SUCCESS, + type: sampleActionTypes.SAMPLESUBMITTED_SUCCESS, sample }; } diff --git a/ServerlessLibraryAPI/ClientApp/src/components/Contribute/AddContributionForm.js b/ServerlessLibraryAPI/ClientApp/src/components/Contribute/AddContributionForm.js index ce39388..da587c3 100644 --- a/ServerlessLibraryAPI/ClientApp/src/components/Contribute/AddContributionForm.js +++ b/ServerlessLibraryAPI/ClientApp/src/components/Contribute/AddContributionForm.js @@ -47,7 +47,7 @@ class AddContributionForm extends Component { libraryService .submitNewSample(this.state) .then( - sample => this.props.addSampleSuccess(sample), + sample => this.props.sampleSubmittedSuccess(sample), error => console.log(error) // todo ) .then(this.resetForm()) @@ -135,7 +135,7 @@ function mapStateToProps(state) { } const mapDispatchToProps = { - addSampleSuccess: sampleActions.addSampleSuccess + sampleSubmittedSuccess: sampleActions.sampleSubmittedSuccess }; const AddContributionFormContainer = connect( diff --git a/ServerlessLibraryAPI/ClientApp/src/reducers/sampleReducer.js b/ServerlessLibraryAPI/ClientApp/src/reducers/sampleReducer.js index e056c7a..959b2b9 100644 --- a/ServerlessLibraryAPI/ClientApp/src/reducers/sampleReducer.js +++ b/ServerlessLibraryAPI/ClientApp/src/reducers/sampleReducer.js @@ -5,8 +5,8 @@ export default function sampleReducer(state = initialState.samples, action) { switch (action.type) { case sampleActionTypes.GETSAMPLES_SUCCESS: return action.samples; - case sampleActionTypes.ADDSAMPLE_SUCCESS: - return [...state, { ...action.sample }]; + case sampleActionTypes.SAMPLESUBMITTED_SUCCESS: + return state; default: return state; } diff --git a/ServerlessLibraryAPI/Controllers/LibraryController.cs b/ServerlessLibraryAPI/Controllers/LibraryController.cs index a485364..68edb48 100644 --- a/ServerlessLibraryAPI/Controllers/LibraryController.cs +++ b/ServerlessLibraryAPI/Controllers/LibraryController.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; using System.Text.RegularExpressions; using ServerlessLibrary.Models; +using Newtonsoft.Json; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 @@ -51,7 +52,7 @@ namespace ServerlessLibrary.Controllers } [HttpPut] - [ProducesResponseType(typeof(LibraryItemWithStats), 200)] + [ProducesResponseType(typeof(LibraryItem), 200)] public IActionResult Put([FromBody]LibraryItem libraryItem) { if (!User.Identity.IsAuthenticated) @@ -77,8 +78,8 @@ namespace ServerlessLibrary.Controllers GitHubUser user = new GitHubUser(User); libraryItem.Author = user.UserName; - this._libraryStore.Add(libraryItem); - return new JsonResult(libraryItem.ConvertTo()); + StorageHelper.submitContributionForApproval(JsonConvert.SerializeObject(libraryItem)); + return new JsonResult(libraryItem); } private static bool IsValidUri(string uriString) diff --git a/ServerlessLibraryAPI/StorageHelper.cs b/ServerlessLibraryAPI/StorageHelper.cs index 2f29c02..7c2825a 100644 --- a/ServerlessLibraryAPI/StorageHelper.cs +++ b/ServerlessLibraryAPI/StorageHelper.cs @@ -1,12 +1,10 @@ -using Microsoft.WindowsAzure.Storage; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Queue; using Microsoft.WindowsAzure.Storage.RetryPolicies; using Microsoft.WindowsAzure.Storage.Table; -using Microsoft.WindowsAzure.Storage.Queue; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Web; namespace ServerlessLibrary { @@ -16,54 +14,67 @@ namespace ServerlessLibrary public class StorageHelper { private const string slItemTableName = "slitemstats"; - private static readonly TableRequestOptions tableRequestRetry = new TableRequestOptions { RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(2), 3) }; + private const string slContributionRequests = "contribution-requests"; + private static readonly TableRequestOptions tableRequestRetry = + new TableRequestOptions { RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(2), 3) }; + private static CloudTableClient tableClient() { // Retrieve storage account from connection string. - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ServerlessLibrarySettings.SLStorageString); + CloudStorageAccount storageAccount = + CloudStorageAccount.Parse(ServerlessLibrarySettings.SLStorageString); // Create the table client. return storageAccount.CreateCloudTableClient(); - } + private static CloudQueueClient cloudQueueClient() { // Retrieve storage account from connection string. - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ServerlessLibrarySettings.SLStorageString); + CloudStorageAccount storageAccount = + CloudStorageAccount.Parse(ServerlessLibrarySettings.SLStorageString); // Create the queue client. return storageAccount.CreateCloudQueueClient(); - } + private static async Task getTableReference(string tableName = slItemTableName) { CloudTable table = tableClient().GetTableReference(tableName); await table.CreateIfNotExistsAsync(); return table; } - private static async Task getQueueReference(string queueName = slItemTableName) + + private static async Task getQueueReference(string queueName) { CloudQueue queue = cloudQueueClient().GetQueueReference(queueName); await queue.CreateIfNotExistsAsync(); return queue; } + + public static async void submitContributionForApproval(string contributionPayload) + { + var message = new CloudQueueMessage(contributionPayload); + await (await getQueueReference(slContributionRequests)).AddMessageAsync(message); + } + public static async void updateUserStats(string statsPayload) { var message = new CloudQueueMessage(statsPayload); - await (await getQueueReference()).AddMessageAsync(message); + await (await getQueueReference(slItemTableName)).AddMessageAsync(message); } + public static async Task> getSLItemRecordsAsync() { - - TableQuery query = new TableQuery().Select(new List { "id", "totalDownloads" - , "likes", "dislikes"}); + TableQuery query = new TableQuery() + .Select(new List { "id", "totalDownloads", "likes", "dislikes" }); TableContinuationToken continuationToken = null; List entities = new List(); var opContext = new OperationContext(); do { - TableQuerySegment - queryResults = await (await getTableReference()).ExecuteQuerySegmentedAsync(query, continuationToken, tableRequestRetry, opContext); + TableQuerySegment queryResults = + await (await getTableReference()).ExecuteQuerySegmentedAsync(query, continuationToken, tableRequestRetry, opContext); continuationToken = queryResults.ContinuationToken; entities.AddRange(queryResults.Results); diff --git a/ServerlessLibraryLogicApp/Deploy-AzureResourceGroup.ps1 b/ServerlessLibraryLogicApp/Deploy-AzureResourceGroup.ps1 new file mode 100644 index 0000000..d787d5b --- /dev/null +++ b/ServerlessLibraryLogicApp/Deploy-AzureResourceGroup.ps1 @@ -0,0 +1,120 @@ +#Requires -Version 3.0 + +Param( + [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, + [string] $ResourceGroupName = 'ServerlessLibrary', + [switch] $UploadArtifacts, + [string] $StorageAccountName, + [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts', + [string] $TemplateFile = 'LogicApp.json', + [string] $TemplateParametersFile = 'LogicApp.parameters.json', + [string] $ArtifactStagingDirectory = '.', + [string] $DSCSourceFolder = 'DSC', + [switch] $ValidateOnly +) + +try { + [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0') +} catch { } + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 3 + +function Format-ValidationOutput { + param ($ValidationOutput, [int] $Depth = 0) + Set-StrictMode -Off + return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) +} + +$OptionalParameters = New-Object -TypeName Hashtable +$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile)) +$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile)) + +if ($UploadArtifacts) { + # Convert relative paths to absolute paths if needed + $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory)) + $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder)) + + # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present + $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json + if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) { + $JsonParameters = $JsonParameters.parameters + } + $ArtifactsLocationName = '_artifactsLocation' + $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken' + $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore + $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore + + # Create DSC configuration archive + if (Test-Path $DSCSourceFolder) { + $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName}) + foreach ($DSCSourceFilePath in $DSCSourceFilePaths) { + $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip' + Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose + } + } + + # Create a storage account name if none was provided + if ($StorageAccountName -eq '') { + $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19) + } + + $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName}) + + # Create the storage account if it doesn't already exist + if ($StorageAccount -eq $null) { + $StorageResourceGroupName = 'ARM_Deploy_Staging' + New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force + $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation" + } + + # Generate the value for artifacts location if it is not provided in the parameter file + if ($OptionalParameters[$ArtifactsLocationName] -eq $null) { + $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName + } + + # Copy files from the local storage staging location to the storage account container + New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1 + + $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName} + foreach ($SourcePath in $ArtifactFilePaths) { + Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) ` + -Container $StorageContainerName -Context $StorageAccount.Context -Force + } + + # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file + if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) { + $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force ` + (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4)) + } +} + +# Create the resource group only when it doesn't already exist +if ((Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -ErrorAction SilentlyContinue) -eq $null) { + New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop +} + +if ($ValidateOnly) { + $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` + -TemplateFile $TemplateFile ` + -TemplateParameterFile $TemplateParametersFile ` + @OptionalParameters) + if ($ErrorMessages) { + Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' + } + else { + Write-Output '', 'Template is valid.' + } +} +else { + New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` + -ResourceGroupName $ResourceGroupName ` + -TemplateFile $TemplateFile ` + -TemplateParameterFile $TemplateParametersFile ` + @OptionalParameters ` + -Force -Verbose ` + -ErrorVariable ErrorMessages + if ($ErrorMessages) { + Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) + } +} \ No newline at end of file diff --git a/ServerlessLibraryLogicApp/Deployment.targets b/ServerlessLibraryLogicApp/Deployment.targets new file mode 100644 index 0000000..0d792ec --- /dev/null +++ b/ServerlessLibraryLogicApp/Deployment.targets @@ -0,0 +1,123 @@ + + + + Debug + AnyCPU + bin\$(Configuration)\ + false + true + false + None + obj\ + $(BaseIntermediateOutputPath)\ + $(BaseIntermediateOutputPath)$(Configuration)\ + $(IntermediateOutputPath)ProjectReferences + $(ProjectReferencesOutputPath)\ + true + + + + false + false + + + + + + + + + + + Always + + + Never + + + false + Build + + + + + + + + _GetDeploymentProjectContent; + _CalculateContentOutputRelativePaths; + _GetReferencedProjectsOutput; + _CalculateArtifactStagingDirectory; + _CopyOutputToArtifactStagingDirectory; + + + + + + + + + + + + + + + + + Configuration=$(Configuration);Platform=$(Platform) + + + + + + + $([System.IO.Path]::GetFileNameWithoutExtension('%(ProjectReference.Identity)')) + + + + + + + $(OutDir) + $(OutputPath) + $(ArtifactStagingDirectory)\ + $(ArtifactStagingDirectory)staging\ + $(Build_StagingDirectory) + + + + + + + <_OriginalIdentity>%(DeploymentProjectContentOutput.Identity) + <_RelativePath>$(_OriginalIdentity.Replace('$(MSBuildProjectDirectory)', '')) + + + + + $(_RelativePath) + + + + + + + + + PrepareForRun + + + + + + + + + + + diff --git a/ServerlessLibraryLogicApp/LogicApp.json b/ServerlessLibraryLogicApp/LogicApp.json new file mode 100644 index 0000000..056b76a --- /dev/null +++ b/ServerlessLibraryLogicApp/LogicApp.json @@ -0,0 +1,388 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "logicAppName": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "metadata": { + "description": "Name of the Logic App." + } + }, + "logicAppLocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "allowedValues": [ + "[resourceGroup().location]", + "eastasia", + "southeastasia", + "centralus", + "eastus", + "eastus2", + "westus", + "northcentralus", + "southcentralus", + "northeurope", + "westeurope", + "japanwest", + "japaneast", + "brazilsouth", + "australiaeast", + "australiasoutheast", + "southindia", + "centralindia", + "westindia", + "canadacentral", + "canadaeast", + "uksouth", + "ukwest", + "westcentralus", + "westus2" + ], + "metadata": { + "description": "Location of the Logic App." + } + }, + "office365_1_Connection_Name": { + "type": "string", + "defaultValue": "office365" + }, + "office365_1_Connection_DisplayName": { + "type": "string", + "defaultValue": "nehagup@microsoft.com" + }, + "documentdb_1_Connection_Name": { + "type": "string", + "defaultValue": "documentdb" + }, + "documentdb_1_Connection_DisplayName": { + "type": "string", + "defaultValue": "slfunctionapptest-cosmosdb" + }, + "documentdb_1_databaseAccount": { + "type": "string", + "metadata": { + "description": "Name of the account without 'documents.azure.com' part" + }, + "defaultValue": "slfunctionapptest" + }, + "documentdb_1_accessKey": { + "type": "securestring", + "metadata": { + "description": "Primary or Secondary Key" + } + }, + "azurequeues_1_Connection_Name": { + "type": "string", + "defaultValue": "azurequeues" + }, + "azurequeues_1_Connection_DisplayName": { + "type": "string", + "defaultValue": "slfunctionapptest-queue" + }, + "azurequeues_1_storageaccount": { + "type": "string", + "metadata": { + "description": "The name of your storage account" + }, + "defaultValue": "slfunctionapptest" + }, + "azurequeues_1_sharedkey": { + "type": "securestring", + "metadata": { + "description": "The shared storage key of your storage account" + } + } + }, + "variables": {}, + "resources": [ + { + "name": "[parameters('logicAppName')]", + "type": "Microsoft.Logic/workflows", + "location": "[parameters('logicAppLocation')]", + "tags": { + "displayName": "LogicApp" + }, + "apiVersion": "2016-06-01", + "properties": { + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "actions": { + "SendApprovalEmail": { + "type": "ApiConnectionWebhook", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "body": { + "NotificationUrl": "@{listCallbackUrl()}", + "Message": { + "To": "serverless-admins@microsoft.com", + "Subject": "Serverless Library Approval Request for sample: \"@{variables('sampleTitle')}\"", + "Options": "Approve, Reject", + "Body": "@triggerBody()?['MessageText']", + "Importance": "Normal" + } + }, + "path": "/approvalmail/$subscriptions" + }, + "runAfter": { + "Initialize_variable": [ + "Succeeded" + ] + } + }, + "IsRequestApproved": { + "type": "If", + "expression": { + "and": [ + { + "equals": [ + "@body('SendApprovalEmail')?['SelectedOption']", + "Approve" + ] + } + ] + }, + "actions": { + "AddContributionToCosmosDB": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['documentdb']['connectionId']" + } + }, + "method": "post", + "body": "@triggerBody()?['MessageText']", + "path": "/dbs/@{encodeURIComponent('serverlesslibrary')}/colls/@{encodeURIComponent('contributions')}/docs" + }, + "runAfter": { + "CopyRequestToApprovedQueue": [ + "Succeeded" + ] + } + }, + "SendApprovalNotificationEmail": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "serverless-admins@microsoft.com", + "Subject": "Request Approved for sample: \"@{variables('sampleTitle')}\"", + "Body": "@triggerBody()?['MessageText']" + }, + "path": "/Mail" + }, + "runAfter": { + "AddContributionToCosmosDB": [ + "Succeeded" + ] + } + }, + "CopyRequestToApprovedQueue": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azurequeues']['connectionId']" + } + }, + "method": "post", + "body": "@triggerBody()?['MessageText']", + "path": "/@{encodeURIComponent('approved-requests')}/messages" + }, + "runAfter": {} + } + }, + "runAfter": { + "SendApprovalEmail": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "SendRejectionNotificationEmail": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "serverless-admins@microsoft.com", + "Subject": "Request Rejected for sample: \"@{variables('sampleTitle')}\"", + "Body": "@triggerBody()?['MessageText']" + }, + "path": "/Mail" + }, + "runAfter": { + "CopyRequestToRejectedQueue": [ + "Succeeded" + ] + } + }, + "CopyRequestToRejectedQueue": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azurequeues']['connectionId']" + } + }, + "method": "post", + "body": "@triggerBody()?['MessageText']", + "path": "/@{encodeURIComponent('rejected-requests')}/messages" + }, + "runAfter": {} + } + } + } + }, + "DeleteRequestFromQueue": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azurequeues']['connectionId']" + } + }, + "method": "delete", + "path": "/@{encodeURIComponent('contribution-requests')}/messages/@{encodeURIComponent(triggerBody()?['MessageId'])}", + "queries": { + "popreceipt": "@triggerBody()?['PopReceipt']" + } + }, + "runAfter": { + "IsRequestApproved": [ + "Succeeded" + ] + } + }, + "Initialize_variable": { + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "sampleTitle", + "type": "String", + "value": "@{json(triggerBody()?['MessageText'])?['title']}" + } + ] + }, + "runAfter": {} + } + }, + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + } + }, + "triggers": { + "When_a_new_contribution_request_is_submitted": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azurequeues']['connectionId']" + } + }, + "method": "get", + "path": "/@{encodeURIComponent('contribution-requests')}/message_trigger" + }, + "recurrence": { + "frequency": "Minute", + "interval": 3 + }, + "splitOn": "@triggerBody()?['QueueMessagesList']?['QueueMessage']" + } + }, + "contentVersion": "1.0.0.0", + "outputs": {} + }, + "parameters": { + "$connections": { + "value": { + "office365": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'office365')]", + "connectionId": "[resourceId('Microsoft.Web/connections', parameters('office365_1_Connection_Name'))]", + "connectionName": "[parameters('office365_1_Connection_Name')]" + }, + "documentdb": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'documentdb')]", + "connectionId": "[resourceId('Microsoft.Web/connections', parameters('documentdb_1_Connection_Name'))]", + "connectionName": "[parameters('documentdb_1_Connection_Name')]" + }, + "azurequeues": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'azurequeues')]", + "connectionId": "[resourceId('Microsoft.Web/connections', parameters('azurequeues_1_Connection_Name'))]", + "connectionName": "[parameters('azurequeues_1_Connection_Name')]" + } + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/connections', parameters('office365_1_Connection_Name'))]", + "[resourceId('Microsoft.Web/connections', parameters('documentdb_1_Connection_Name'))]", + "[resourceId('Microsoft.Web/connections', parameters('azurequeues_1_Connection_Name'))]" + ] + }, + { + "type": "MICROSOFT.WEB/CONNECTIONS", + "apiVersion": "2016-06-01", + "name": "[parameters('office365_1_Connection_Name')]", + "location": "[parameters('logicAppLocation')]", + "properties": { + "api": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'office365')]" + }, + "displayName": "[parameters('office365_1_Connection_DisplayName')]" + } + }, + { + "type": "MICROSOFT.WEB/CONNECTIONS", + "apiVersion": "2016-06-01", + "name": "[parameters('documentdb_1_Connection_Name')]", + "location": "[parameters('logicAppLocation')]", + "properties": { + "api": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'documentdb')]" + }, + "displayName": "[parameters('documentdb_1_Connection_DisplayName')]", + "parameterValues": { + "databaseAccount": "[parameters('documentdb_1_databaseAccount')]", + "accessKey": "[parameters('documentdb_1_accessKey')]" + } + } + }, + { + "type": "MICROSOFT.WEB/CONNECTIONS", + "apiVersion": "2016-06-01", + "name": "[parameters('azurequeues_1_Connection_Name')]", + "location": "[parameters('logicAppLocation')]", + "properties": { + "api": { + "id": "[concat(subscription().id, '/providers/Microsoft.Web/locations/', parameters('logicAppLocation'), '/managedApis/', 'azurequeues')]" + }, + "displayName": "[parameters('azurequeues_1_Connection_DisplayName')]", + "parameterValues": { + "storageaccount": "[parameters('azurequeues_1_storageaccount')]", + "sharedkey": "[parameters('azurequeues_1_sharedkey')]" + } + } + } + ], + "outputs": {} +} \ No newline at end of file diff --git a/ServerlessLibraryLogicApp/LogicApp.parameters.json b/ServerlessLibraryLogicApp/LogicApp.parameters.json new file mode 100644 index 0000000..3f90652 --- /dev/null +++ b/ServerlessLibraryLogicApp/LogicApp.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "logicAppName": { + "value": "ApprovalWorkflow" + } + } +} \ No newline at end of file diff --git a/ServerlessLibraryLogicApp/ServerlessLibraryLogicApp.deployproj b/ServerlessLibraryLogicApp/ServerlessLibraryLogicApp.deployproj new file mode 100644 index 0000000..dd2cf20 --- /dev/null +++ b/ServerlessLibraryLogicApp/ServerlessLibraryLogicApp.deployproj @@ -0,0 +1,34 @@ + + + + + Debug + AnyCPU + + + Release + AnyCPU + + + + 92dd9a5d-3a93-493c-8f69-23a1c519a1c4 + + + + + + + + + + + + + False + + + + + + + \ No newline at end of file