diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/.gitignore b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Blacksmiths.Functions.csproj b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Blacksmiths.Functions.csproj new file mode 100644 index 0000000..8f178e0 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Blacksmiths.Functions.csproj @@ -0,0 +1,38 @@ + + + net8.0 + v4 + Exe + enable + enable + Cloudheim.Blacksmiths + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Functions/Armaments/Swords.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Functions/Armaments/Swords.cs new file mode 100644 index 0000000..83e5187 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Functions/Armaments/Swords.cs @@ -0,0 +1,118 @@ +namespace Cloudheim.Blacksmiths.Functions.Armaments; + +/// +/// Azure Functions for working with swords. +/// +/// The logger to use for logging. +/// The to use when working with swords. +public class Swords(ILogger logger, ArmamentService armamentService, JsonSerializerOptions jsonSerializerOptions1) +{ + + private readonly ILogger _logger = logger; + private readonly ArmamentService _armamentService = armamentService; + private readonly JsonSerializerOptions _jsonSerializerOptions = jsonSerializerOptions1; + + /// + /// Creates a new sword. + /// + /// The HTTP request containing the sword details. + /// An IActionResult representing the result of the operation. + [Function("Swords_Create")] + public async Task CreateSwordAsync([HttpTrigger(AuthorizationLevel.Function, "post", Route = "armaments/swords")] HttpRequest request) + { + _logger.LogInformation("Received request to create a new sword."); + + SwordRequest? swordRequest = JsonSerializer.Deserialize(await new StreamReader(request.Body).ReadToEndAsync(), _jsonSerializerOptions); + if (swordRequest is null) + { + _logger.LogError("Failed to deserialize the request body."); + return new BadRequestResult(); + } + + _logger.LogInformation("Processing request to create the '{swordName}' sword.", swordRequest.Name); + SwordResponse swordResponse = await _armamentService.CreateSwordAsync(swordRequest); + return new AcceptedResult($"armaments/swords/{swordResponse.Id}", swordResponse); + } + + /// + /// Gets a sword by its ID. + /// + /// The HTTP request containing the sword details. + /// The identifier of the sword to retrieve. + /// An IActionResult representing the result of the operation. + [Function("Swords_Get")] + public async Task GetSwordAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "armaments/swords/{id}")] HttpRequest request, string id) + { + _logger.LogInformation("Received request to get the '{id}' sword.", id); + SwordResponse swordResponse = await _armamentService.GetSwordAsync(id); + if (swordResponse is null) + { + _logger.LogError("Failed to find the '{id}' sword.", id); + return new NotFoundResult(); + } + return new OkObjectResult(swordResponse); + } + + /// + /// Gets a list of all swords. + /// + /// The HTTP request containing the sword details. + /// An IActionResult representing the result of the operation. + [Function("Swords_GetList")] + public async Task GetSwordsAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "armaments/swords")] HttpRequest request) + { + _logger.LogInformation("Received request to get all swords."); + IEnumerable swordResponses = await _armamentService.GetSwordsAsync(); + return new OkObjectResult(swordResponses); + } + + /// + /// Updates a sword by its identifier. + /// + /// The HTTP request containing the sword details. + /// The identifier of the sword to update. + /// An IActionResult representing the result of the operation. + [Function("Swords_Update")] + public async Task UpdateSwordAsync([HttpTrigger(AuthorizationLevel.Function, "put", Route = "armaments/swords/{id}")] HttpRequest request, string id) + { + _logger.LogInformation("Received request to update the '{id}' sword.", id); + + SwordRequest? swordRequest = JsonSerializer.Deserialize(await new StreamReader(request.Body).ReadToEndAsync()); + if (swordRequest is null) + { + _logger.LogError("Failed to deserialize the request body."); + return new BadRequestResult(); + } + + SwordResponse swordResponse = await _armamentService.UpdateSwordAsync(id, swordRequest); + if (swordResponse is null) + { + _logger.LogError("Failed to find the '{id}' sword.", id); + return new NotFoundResult(); + } + return new OkObjectResult(swordResponse); + } + + /// + /// Deletes a sword by its identifier. + /// + /// The HTTP request containing the sword details. + /// The identifier of the sword to delete. + /// An IActionResult representing the result of the operation. + [Function("Swords_Delete")] + public async Task DeleteSwordAsync([HttpTrigger(AuthorizationLevel.Function, "delete", Route = "armaments/swords/{id}")] HttpRequest request, string id) + { + try + { + _logger.LogInformation("Received request to delete the '{id}' sword.", id); + await _armamentService.DeleteSwordAsync(id); + return new NoContentResult(); + } + catch (NotFoundException) + { + _logger.LogError("Failed to find the '{id}' sword.", id); + return new NotFoundResult(); + } + } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/GlobalUsings.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/GlobalUsings.cs new file mode 100644 index 0000000..1f10819 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/GlobalUsings.cs @@ -0,0 +1,9 @@ +global using Cloudheim.Blacksmiths.Armaments; +global using Cloudheim.Blacksmiths.Armaments.Requests; +global using Cloudheim.Blacksmiths.Armaments.Responses; +global using Cloudheim.Common.Exceptions; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.Azure.Functions.Worker; +global using Microsoft.Extensions.Logging; +global using System.Text.Json; \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Program.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Program.cs new file mode 100644 index 0000000..f02c2ac --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Program.cs @@ -0,0 +1,47 @@ +using Azure.Data.AppConfiguration; +using Azure.Identity; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Text.Json.Serialization; + +DefaultAzureCredential defaultAzureCredential = new(); + +Uri appConfigEndpoint = new(Environment.GetEnvironmentVariable("AppConfigEndpoint")!); +ConfigurationClient configClient = new(appConfigEndpoint, defaultAzureCredential); + +CosmosClient cosmosClient = new(configClient.GetConfigurationSetting("Cosmos:AccountEndpoint").Value.Value, defaultAzureCredential); +Database database = cosmosClient.GetDatabase(configClient.GetConfigurationSetting("Cosmos:CloudheimDatabase").Value.Value); +Container blacksmithsContainer = database.GetContainer(configClient.GetConfigurationSetting("Cosmos:BlacksmithsContainer").Value.Value); + +string armamentsPartitionKey = "category"; +string swordCategory = "Swords"; + +JsonSerializerOptions jsonSerializerOptions = new() +{ + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull +}; + +IHost host = new HostBuilder() + .ConfigureAppConfiguration(builder => + { + builder.AddAzureAppConfiguration(options => + { + options.Connect(appConfigEndpoint, defaultAzureCredential); + }); + }) + + .ConfigureFunctionsWebApplication() + .ConfigureServices(services => + { + services.AddApplicationInsightsTelemetryWorkerService(); + services.ConfigureFunctionsApplicationInsights(); + services.AddSingleton(jsonSerializerOptions); + services.AddSingleton(new ArmamentService(blacksmithsContainer, armamentsPartitionKey, swordCategory)); + }) + .Build(); + +host.Run(); diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/appInsights1.arm.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/appInsights1.arm.json new file mode 100644 index 0000000..bdbe1a4 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/appInsights1.arm.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceGroupName": { + "type": "string", + "defaultValue": "rg-Nordheim", + "metadata": { + "_parameterType": "resourceGroup", + "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." + } + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "eastus2", + "metadata": { + "_parameterType": "location", + "description": "Location of the resource group. Resource groups could have different location than resources." + } + }, + "resourceLocation": { + "type": "string", + "defaultValue": "[parameters('resourceGroupLocation')]", + "metadata": { + "_parameterType": "location", + "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "apiVersion": "2019-10-01" + }, + { + "type": "Microsoft.Resources/deployments", + "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('appi-blacksmiths-demo-use2', subscription().subscriptionId)))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "apiVersion": "2019-10-01", + "dependsOn": [ + "[parameters('resourceGroupName')]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "kind": "web", + "name": "appi-blacksmiths-demo-use2", + "type": "microsoft.insights/components", + "location": "[parameters('resourceLocation')]", + "properties": {}, + "apiVersion": "2015-05-01" + } + ] + } + } + } + ], + "metadata": { + "_dependencyType": "appInsights.azure" + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/storage1.arm.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/storage1.arm.json new file mode 100644 index 0000000..dd58bd9 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/ServiceDependencies/func-blacksmiths-demo-use2 - Zip Deploy/storage1.arm.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceGroupName": { + "type": "string", + "defaultValue": "rg-Nordheim", + "metadata": { + "_parameterType": "resourceGroup", + "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." + } + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "eastus2", + "metadata": { + "_parameterType": "location", + "description": "Location of the resource group. Resource groups could have different location than resources." + } + }, + "resourceLocation": { + "type": "string", + "defaultValue": "[parameters('resourceGroupLocation')]", + "metadata": { + "_parameterType": "location", + "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "apiVersion": "2019-10-01" + }, + { + "type": "Microsoft.Resources/deployments", + "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('stblacksmithsdemouse2s', subscription().subscriptionId)))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "apiVersion": "2019-10-01", + "dependsOn": [ + "[parameters('resourceGroupName')]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "kind": "Storage", + "name": "stblacksmithsdemouse2s", + "type": "Microsoft.Storage/storageAccounts", + "location": "[parameters('resourceLocation')]", + "apiVersion": "2017-10-01" + } + ] + } + } + } + ], + "metadata": { + "_dependencyType": "storage.azure" + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/launchSettings.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/launchSettings.json new file mode 100644 index 0000000..7763d6f --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "Blacksmiths.Functions": { + "commandName": "Project", + "commandLineArgs": "--port 7247", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.func-blacksmiths-demo-use2 - Zip Deploy.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.func-blacksmiths-demo-use2 - Zip Deploy.json new file mode 100644 index 0000000..60fc10e --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.func-blacksmiths-demo-use2 - Zip Deploy.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "appInsights1": { + "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/microsoft.insights/components/appi-blacksmiths-demo-use2", + "type": "appInsights.azure", + "connectionId": "APPLICATIONINSIGHTS_CONNECTION_STRING" + }, + "storage1": { + "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.Storage/storageAccounts/stblacksmithsdemouse2s", + "type": "storage.azure", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.json new file mode 100644 index 0000000..4dd197a --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights", + "connectionId": "APPLICATIONINSIGHTS_CONNECTION_STRING" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.local.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..b804a28 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/Properties/serviceDependencies.local.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/host.json b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/host.json new file mode 100644 index 0000000..7a10789 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Functions/host.json @@ -0,0 +1,17 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + }, + "extensions": { + "http": { + "routePrefix": "" + } + } +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/ArmamentService.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/ArmamentService.cs new file mode 100644 index 0000000..c90dc47 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/ArmamentService.cs @@ -0,0 +1,26 @@ +using Cloudheim.Common.Services; + +namespace Cloudheim.Blacksmiths.Armaments; + +public class ArmamentService(Container container, string partitionKey, string category) : ServicesBase(container, partitionKey, category) +{ + + public async Task CreateSwordAsync(SwordRequest request) + { + SwordDocument swordDocument = request.ToDocument(); + return (await CreateAsync(swordDocument, swordDocument.Category)).ToResponse(); + } + + public async Task GetSwordAsync(string id) + => (await GetAsync(id)).ToResponse(); + + public async Task> GetSwordsAsync() + => (await GetAsync()).Select(document => document.ToResponse()); + + public async Task UpdateSwordAsync(string id, SwordRequest request) + => (await UpdateAsync(id, request.ToDocument())).ToResponse(); + + public async Task DeleteSwordAsync(string id) + => await DeleteAsync(id); + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Documents/SwordDocument.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Documents/SwordDocument.cs new file mode 100644 index 0000000..23dbf9c --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Documents/SwordDocument.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; + +namespace Cloudheim.Blacksmiths.Armaments.Documents; + +/// +/// Represents a document for a sword in the armaments system. +/// +public class SwordDocument : BlacksmithDocumentBase +{ + + private const string _category = "Swords"; + + /// + /// Initializes a new instance of the class. + /// + public SwordDocument() : base(_category) { } + + public SwordDocument(string Id) : base(_category) + { + this.Id = Id; + } + + /// + /// Gets or sets the name of the sword. + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the type of the sword. + /// + [JsonProperty(PropertyName = "type")] + public string Type { get; set; } = null!; + + /// + /// Gets or sets the material of the sword. + /// + [JsonProperty(PropertyName = "material")] + public string Material { get; set; } = null!; + + /// + /// Gets or sets the attributes of the sword. + /// + [JsonProperty(PropertyName = "attributes")] + public SwordAttributes Attributes { get; set; } = null!; + + /// + /// Gets or sets the date and time when the sword was requested. + /// + [JsonProperty(PropertyName = "requestedAt")] + public DateTime RequestedAt { get; set; } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordDocumentExtensions.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordDocumentExtensions.cs new file mode 100644 index 0000000..82414cc --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordDocumentExtensions.cs @@ -0,0 +1,21 @@ +using Cloudheim.Blacksmiths.Armaments.Responses; + +namespace Cloudheim.Blacksmiths.Armaments.Extensions; + +internal static class SwordDocumentExtensions +{ + + internal static SwordResponse ToResponse(this SwordDocument document) + { + return new SwordResponse + { + Id = document.Id, + Name = document.Name, + Type = document.Type, + Material = document.Material, + Attributes = document.Attributes, + RequestedAt = document.RequestedAt + }; + } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordRequestExtensions.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordRequestExtensions.cs new file mode 100644 index 0000000..41e4883 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Extensions/SwordRequestExtensions.cs @@ -0,0 +1,26 @@ +namespace Cloudheim.Blacksmiths.Armaments.Extensions; + +/// +/// Provides extension methods for converting SwordRequest objects to SwordDocument objects. +/// +internal static class SwordRequestExtensions +{ + + /// + /// Converts a SwordRequest object to a SwordDocument object. + /// + /// The SwordRequest object to convert. + /// A new SwordDocument object. + internal static SwordDocument ToDocument(this SwordRequest request) + { + return new SwordDocument(Guid.NewGuid().ToString()) + { + Name = request.Name, + Type = request.Type, + Material = request.Material, + Attributes = request.Attributes, + RequestedAt = DateTime.UtcNow + }; + } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordAttributes.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordAttributes.cs new file mode 100644 index 0000000..d11a8f0 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordAttributes.cs @@ -0,0 +1,29 @@ +namespace Cloudheim.Blacksmiths.Armaments.Requests; + +/// +/// Represents the attributes of a sword. +/// +public class SwordAttributes +{ + + /// + /// Gets or sets the sharpness of the sword. + /// + public string Sharpness { get; set; } = null!; + + /// + /// Gets or sets the engraving on the sword. + /// + public string Engraving { get; set; } = null!; + + /// + /// Gets or sets the weight of the sword. + /// + public decimal Weight { get; set; } + + /// + /// Gets or sets the length of the sword. + /// + public int Length { get; set; } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordRequest.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordRequest.cs new file mode 100644 index 0000000..a581535 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Requests/SwordRequest.cs @@ -0,0 +1,29 @@ +namespace Cloudheim.Blacksmiths.Armaments.Requests; + +/// +/// Represents a request for a sword. +/// +public class SwordRequest +{ + + /// + /// Gets or sets the name of the sword. + /// + public string Name { get; set; } = null!; + + /// + /// Gets or sets the type of the sword. + /// + public string Type { get; set; } = null!; + + /// + /// Gets or sets the material of the sword. + /// + public string Material { get; set; } = null!; + + /// + /// Gets or sets the attributes of the sword. + /// + public SwordAttributes Attributes { get; set; } = null!; + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Responses/SwordResponse.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Responses/SwordResponse.cs new file mode 100644 index 0000000..0ba515b --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Armaments/Responses/SwordResponse.cs @@ -0,0 +1,39 @@ +namespace Cloudheim.Blacksmiths.Armaments.Responses; + +/// +/// Represents a response for a sword. +/// +public class SwordResponse +{ + + /// + /// Gets or sets the ID of the sword. + /// + public string Id { get; set; } = null!; + + /// + /// Gets or sets the name of the sword. + /// + public string Name { get; set; } = null!; + + /// + /// Gets or sets the type of the sword. + /// + public string Type { get; set; } = null!; + + /// + /// Gets or sets the material of the sword. + /// + public string Material { get; set; } = null!; + + /// + /// Gets or sets the attributes of the sword. + /// + public SwordAttributes Attributes { get; set; } = null!; + + /// + /// Gets or sets the date and time when the sword was requested. + /// + public DateTime RequestedAt { get; set; } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Blacksmiths.Services.csproj b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Blacksmiths.Services.csproj new file mode 100644 index 0000000..272e3bb --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Blacksmiths.Services.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + Cloudheim.Blacksmiths + + + + + + + + + + + + + diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Documents/BlacksmithDocumentBase.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Documents/BlacksmithDocumentBase.cs new file mode 100644 index 0000000..c615c71 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/Documents/BlacksmithDocumentBase.cs @@ -0,0 +1,13 @@ +namespace Cloudheim.Blacksmiths.Documents; + +/// +/// Represents a base class for blacksmith documents. +/// +/// +/// Initializes a new instance of the class with the specified category. +/// +/// The category of the blacksmith document. +public abstract class BlacksmithDocumentBase(string category) : DocumentBase(_house, category) +{ + private const string _house = "Blacksmiths"; +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/GlobalUsings.cs b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/GlobalUsings.cs new file mode 100644 index 0000000..7f25c11 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Blacksmiths/Blacksmiths.Services/GlobalUsings.cs @@ -0,0 +1,9 @@ +global using Cloudheim.Blacksmiths.Armaments.Documents; +global using Cloudheim.Blacksmiths.Armaments.Extensions; +global using Cloudheim.Blacksmiths.Armaments.Requests; +global using Cloudheim.Blacksmiths.Armaments.Responses; +global using Cloudheim.Blacksmiths.Documents; +global using Cloudheim.Common.Documents; +global using Cloudheim.Common.Exceptions; +global using Microsoft.Azure.Cosmos; +global using System.Text.Json.Serialization; \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Common/Common.csproj b/Demos/ServelessAPICentricArchitecture/Common/Common.csproj new file mode 100644 index 0000000..925a2ce --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Common/Common.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + Cloudheim.Common + + + + + + + + + diff --git a/Demos/ServelessAPICentricArchitecture/Common/Documents/DocumentBase.cs b/Demos/ServelessAPICentricArchitecture/Common/Documents/DocumentBase.cs new file mode 100644 index 0000000..1b7b631 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Common/Documents/DocumentBase.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace Cloudheim.Common.Documents; + +/// +/// Base class for documents. +/// +/// +/// Initializes a new instance of the class. +/// +/// The house of the document. +/// The category of the document. +public abstract class DocumentBase(string house, string category) +{ + + /// + /// Gets or sets the ID of the document. + /// + //[JsonPropertyName("id")] + [JsonProperty(PropertyName = "id")] + public string Id { get; protected set; } = Guid.NewGuid().ToString(); + + /// + /// Gets or sets the house of the document. + /// + [JsonProperty(PropertyName = "house")] + public string House { get; protected set; } = house; + + /// + /// Gets or sets the category of the document. + /// + [JsonProperty(PropertyName = "category")] + public string Category { get; protected set; } = category; + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Common/Exceptions/NotFoundException.cs b/Demos/ServelessAPICentricArchitecture/Common/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..8df71b9 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Common/Exceptions/NotFoundException.cs @@ -0,0 +1,25 @@ +namespace Cloudheim.Common.Exceptions; + +/// +/// Represents an exception that is thrown when a specified document cannot be located. +/// +public class NotFoundException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public NotFoundException() : base("Failed to locate the specified document.") { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public NotFoundException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public NotFoundException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/Demos/ServelessAPICentricArchitecture/Common/Exceptions/SaveFailedException.cs b/Demos/ServelessAPICentricArchitecture/Common/Exceptions/SaveFailedException.cs new file mode 100644 index 0000000..969f089 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Common/Exceptions/SaveFailedException.cs @@ -0,0 +1,27 @@ +namespace Cloudheim.Common.Exceptions; + +/// +/// Represents an exception that is thrown when a save operation fails. +/// +public class SaveFailedException : Exception +{ + + /// + /// Initializes a new instance of the class. + /// + public SaveFailedException() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public SaveFailedException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public SaveFailedException(string message, Exception innerException) : base(message, innerException) { } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/Common/Services/ServicesBase.cs b/Demos/ServelessAPICentricArchitecture/Common/Services/ServicesBase.cs new file mode 100644 index 0000000..6acc901 --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/Common/Services/ServicesBase.cs @@ -0,0 +1,98 @@ +using Cloudheim.Common.Exceptions; +using Microsoft.Azure.Cosmos; + +namespace Cloudheim.Common.Services; + +public abstract class ServicesBase(Container container, string partitionKey, string category) +{ + + private readonly Container _container = container; + private readonly string _partitionKey = partitionKey; + private readonly string _category = category; + + protected async Task CreateAsync(TDocument document, string partitionKey) where TDocument : class + { + try + { + //ItemResponse response = await _container.CreateItemAsync(document, new PartitionKey(_partitionKey)); + ItemResponse response = await _container.CreateItemAsync(document, new PartitionKey(partitionKey)); + if (response.StatusCode != System.Net.HttpStatusCode.Created) + throw new SaveFailedException($"Failed to process the {_category}. Error code: {response.StatusCode}."); + return response.Resource; + } + catch (SaveFailedException) + { + throw; + } + catch (Exception ex) + { + throw new SaveFailedException($"Failed to process the {_category}.", ex); + } + } + + protected async Task GetAsync(string id) where TDocument : class + { + ItemResponse response = await _container.ReadItemAsync(id, new PartitionKey(_partitionKey)); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + throw new NotFoundException($"The {_category} with the identifier '{id}' was not found."); + return response.Resource; + } + + protected async Task> GetAsync() where TDocument : class + { + QueryDefinition query = new($"SELECT * FROM items WHERE items.category = '{_category}'"); + using FeedIterator feed = _container.GetItemQueryIterator(query); + List results = []; + while (feed.HasMoreResults) + { + FeedResponse response = await feed.ReadNextAsync(); + results.AddRange(response); + } + return results; + } + + protected async Task UpdateAsync(string id, TDocument document) where TDocument : class + { + try + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException($"The {_category} identifier must be provided.", nameof(id)); + ItemResponse response = await _container.ReadItemAsync(id, new PartitionKey(_partitionKey)); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + throw new NotFoundException($"The {_category} with the identifier '{id}' was not found."); + response = await _container.ReplaceItemAsync(document, id, new PartitionKey(_partitionKey)); + return response.Resource; + } + catch (NotFoundException) + { + throw; + } + catch (ArgumentException) + { + throw; + } + catch (Exception ex) + { + throw new SaveFailedException($"Failed to process the {_category}.", ex); + } + } + + protected async Task DeleteAsync(string id) + { + try + { + ItemResponse response = await _container.DeleteItemAsync(id, new PartitionKey(_partitionKey)); + if (response.StatusCode != System.Net.HttpStatusCode.NoContent) + throw new SaveFailedException($"Failed to delete the {_category}. Error code: {response.StatusCode}."); + } + catch (SaveFailedException) + { + throw; + } + catch (Exception ex) + { + throw new SaveFailedException($"Failed to delete the {_category}.", ex); + } + } + +} \ No newline at end of file diff --git a/Demos/ServelessAPICentricArchitecture/ServelessAPICentricArchitecture.sln b/Demos/ServelessAPICentricArchitecture/ServelessAPICentricArchitecture.sln new file mode 100644 index 0000000..98db83c --- /dev/null +++ b/Demos/ServelessAPICentricArchitecture/ServelessAPICentricArchitecture.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35209.166 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blacksmiths", "Blacksmiths", "{1380D375-4799-46A0-8858-F22B425BCDC6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{C4297938-7AD3-45EF-A4EF-02A587DBFDD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{239087C2-C674-4630-AACF-E0B0D5DDD396}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blacksmiths.Services", "Blacksmiths\Blacksmiths.Services\Blacksmiths.Services.csproj", "{A1758414-F4B5-4268-BB33-63DDCE547883}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blacksmiths.Functions", "Blacksmiths\Blacksmiths.Functions\Blacksmiths.Functions.csproj", "{0C3D0329-0AD7-4F33-B43A-A74944426694}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {239087C2-C674-4630-AACF-E0B0D5DDD396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {239087C2-C674-4630-AACF-E0B0D5DDD396}.Debug|Any CPU.Build.0 = Debug|Any CPU + {239087C2-C674-4630-AACF-E0B0D5DDD396}.Release|Any CPU.ActiveCfg = Release|Any CPU + {239087C2-C674-4630-AACF-E0B0D5DDD396}.Release|Any CPU.Build.0 = Release|Any CPU + {A1758414-F4B5-4268-BB33-63DDCE547883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1758414-F4B5-4268-BB33-63DDCE547883}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1758414-F4B5-4268-BB33-63DDCE547883}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1758414-F4B5-4268-BB33-63DDCE547883}.Release|Any CPU.Build.0 = Release|Any CPU + {0C3D0329-0AD7-4F33-B43A-A74944426694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C3D0329-0AD7-4F33-B43A-A74944426694}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C3D0329-0AD7-4F33-B43A-A74944426694}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C3D0329-0AD7-4F33-B43A-A74944426694}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {239087C2-C674-4630-AACF-E0B0D5DDD396} = {C4297938-7AD3-45EF-A4EF-02A587DBFDD5} + {A1758414-F4B5-4268-BB33-63DDCE547883} = {1380D375-4799-46A0-8858-F22B425BCDC6} + {0C3D0329-0AD7-4F33-B43A-A74944426694} = {1380D375-4799-46A0-8858-F22B425BCDC6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B5C10CB1-99D2-4583-A081-3333502A069B} + EndGlobalSection +EndGlobal diff --git a/Demos/readme.md b/Demos/readme.md new file mode 100644 index 0000000..d5cf7f4 --- /dev/null +++ b/Demos/readme.md @@ -0,0 +1 @@ +Add demo code for the presentation here. Delete this readme file once you have added demo code to the folder. If there is no demo code for the presentations (slides only presentation), delete the Demos folder.