From b311c540e0b53cad1d289d08417b8c546899dc1e Mon Sep 17 00:00:00 2001 From: Kashif Saadat Date: Wed, 3 Jul 2024 14:23:43 +0100 Subject: [PATCH 1/2] feat: initial commit --- .dockerignore | 30 ++ .gitignore | 501 ++++++++++++++++++ .vscode/launch.json | 35 ++ .vscode/tasks.json | 41 ++ LICENSE | 21 + README.md | 32 ++ WebApi/Controllers/ProductsController.cs | 74 +++ .../Controllers/WeatherForecastController.cs | 33 ++ WebApi/Dockerfile | 21 + WebApi/MainDbContext/MainDemoDbContext.cs | 13 + WebApi/MainDbContext/Product.cs | 14 + .../20240419235045_InitialCreate.Designer.cs | 56 ++ .../20240419235045_InitialCreate.cs | 38 ++ .../MainDemoDbContextModelSnapshot.cs | 53 ++ WebApi/Models/ProductDto.cs | 11 + WebApi/Program.cs | 40 ++ WebApi/Properties/launchSettings.json | 52 ++ WebApi/WeatherForecast.cs | 13 + WebApi/WebApi.csproj | 26 + WebApi/WebApi.http | 6 + WebApi/appsettings.Development.json | 8 + WebApi/appsettings.Production.json | 12 + WebApi/appsettings.json | 12 + compose.yml | 38 ++ 24 files changed, 1180 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 LICENSE create mode 100644 WebApi/Controllers/ProductsController.cs create mode 100644 WebApi/Controllers/WeatherForecastController.cs create mode 100644 WebApi/Dockerfile create mode 100644 WebApi/MainDbContext/MainDemoDbContext.cs create mode 100644 WebApi/MainDbContext/Product.cs create mode 100644 WebApi/Migrations/20240419235045_InitialCreate.Designer.cs create mode 100644 WebApi/Migrations/20240419235045_InitialCreate.cs create mode 100644 WebApi/Migrations/MainDemoDbContextModelSnapshot.cs create mode 100644 WebApi/Models/ProductDto.cs create mode 100644 WebApi/Program.cs create mode 100644 WebApi/Properties/launchSettings.json create mode 100644 WebApi/WeatherForecast.cs create mode 100644 WebApi/WebApi.csproj create mode 100644 WebApi/WebApi.http create mode 100644 WebApi/appsettings.Development.json create mode 100644 WebApi/appsettings.Production.json create mode 100644 WebApi/appsettings.json create mode 100644 compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..588f7cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,501 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.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 + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# 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 +# Note: 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 +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable 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 +*.appx +*.appxbundle +*.appxupload + +# 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 +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# 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 +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# 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/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp + +#Ignore thumbnails created by Windows +#Ignore files built by Visual Studio +*.exe +*.bak +*.cache +[Bb]in +[Dd]ebug*/ +*.lib +obj/ +[Rr]elease*/ +[Tt]est[Rr]esult* +#Nuget packages folder +packages/ +*.dll + +App/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..41acaa7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // 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/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/WebApi/bin/Debug/net8.0/WebApi.dll", + "args": [], + "cwd": "${workspaceFolder}/WebApi", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a18514d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/WebApi/WebApi.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/WebApi/WebApi.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/WebApi/WebApi.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..01312d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Appvia Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e69de29..6ce2943 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,32 @@ +# ASP.Net Web API + +## Description + +This project is a simple web api built using ASP.NET, and connecting to an SQL Server database. The application allows users to perform CRUD operations on a list of items. + +## Features + +- /swagger endpoint for API documentation +- CRUD operations on a list of product items + +## Installation + +1. Clone the repository: `git clone https://github.com/appvia/aspnet-app.git` +2. Run docker compose up --build --detach + +## Usage + +1. Open the app in your browser: `http://localhost:8080/swagger` (may take a few seconds to start up) +2. Expand the API endpoints and "Try it out" + +## Published Images + +The images are published on Github Container Registry: https://github.com/appvia/todo-app/pkgs/container/aspnet-app + +## Contributing + +Contributions are welcome! If you find a bug or have a feature request, please open an issue or submit a pull request. + +## License + +This project is licensed under the [MIT License](LICENSE). diff --git a/WebApi/Controllers/ProductsController.cs b/WebApi/Controllers/ProductsController.cs new file mode 100644 index 0000000..b98ec35 --- /dev/null +++ b/WebApi/Controllers/ProductsController.cs @@ -0,0 +1,74 @@ +using WebApi.MainDbContext; +using WebApi.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ProductsController : ControllerBase + { + private readonly MainDemoDbContext _mainDbContext; + private readonly ILogger _logger; + public ProductsController(ILogger logger, MainDemoDbContext mainDbContext) + { + _logger = logger; + _mainDbContext = mainDbContext; + } + + [HttpGet] + public async Task> Get() + { + return await _mainDbContext.Products.ToListAsync(); + } + + + [HttpPost] + public async Task Post(ProductDto productDto) + { + var product = new Product() + { + Name = productDto.Name, + Weight = productDto.Weight, + InsertDate = DateTime.Now, + UpdateDate = DateTime.Now + }; + _mainDbContext.Add(product); + await _mainDbContext.SaveChangesAsync(); + + return product; + } + + [HttpPut("{productId}")] + public async Task Put(int productId, ProductDto productDto) + { + var product = await _mainDbContext.Products.FindAsync(productId); + if (product == null) + { + throw new Exception("Couldn't find the product"); + } + product.Name = productDto.Name; + product.Weight = productDto.Weight; + product.UpdateDate = DateTime.Now; + _mainDbContext.Update(product); + + await _mainDbContext.SaveChangesAsync(); + + return product; + } + + [HttpDelete("{productId}")] + public async Task Delete(int productId) + { + var product = await _mainDbContext.Products.FindAsync(productId); + if (product == null) + { + throw new Exception("Couldn't find the product"); + } + _mainDbContext.Remove(product); + await _mainDbContext.SaveChangesAsync(); + return product; + } + } +} diff --git a/WebApi/Controllers/WeatherForecastController.cs b/WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..e08016f --- /dev/null +++ b/WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/WebApi/Dockerfile b/WebApi/Dockerfile new file mode 100644 index 0000000..b14df14 --- /dev/null +++ b/WebApi/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY . . +RUN dotnet restore "WebApi.csproj" +RUN dotnet build "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "WebApi.dll"] diff --git a/WebApi/MainDbContext/MainDemoDbContext.cs b/WebApi/MainDbContext/MainDemoDbContext.cs new file mode 100644 index 0000000..ce391bc --- /dev/null +++ b/WebApi/MainDbContext/MainDemoDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace WebApi.MainDbContext +{ + public class MainDemoDbContext : DbContext + { + public MainDemoDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Products { get; set; } + } +} diff --git a/WebApi/MainDbContext/Product.cs b/WebApi/MainDbContext/Product.cs new file mode 100644 index 0000000..9988da0 --- /dev/null +++ b/WebApi/MainDbContext/Product.cs @@ -0,0 +1,14 @@ + + +namespace WebApi.MainDbContext +{ + public class Product + { + public int Id { get; set; } + public string Name { get; set; } + public decimal Weight { get; set; } + public DateTime InsertDate { get; set; } + public DateTime UpdateDate { get; set; } + } + +} \ No newline at end of file diff --git a/WebApi/Migrations/20240419235045_InitialCreate.Designer.cs b/WebApi/Migrations/20240419235045_InitialCreate.Designer.cs new file mode 100644 index 0000000..19af237 --- /dev/null +++ b/WebApi/Migrations/20240419235045_InitialCreate.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using WebApi.MainDbContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace WebApi.Migrations +{ + [DbContext(typeof(MainDemoDbContext))] + [Migration("20240419235045_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("WebApi.MainDbContext.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("InsertDate") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("Weight") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WebApi/Migrations/20240419235045_InitialCreate.cs b/WebApi/Migrations/20240419235045_InitialCreate.cs new file mode 100644 index 0000000..eff01da --- /dev/null +++ b/WebApi/Migrations/20240419235045_InitialCreate.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WebApi.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Weight = table.Column(type: "decimal(18,2)", nullable: false), + InsertDate = table.Column(type: "datetime2", nullable: false), + UpdateDate = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Products"); + } + } +} diff --git a/WebApi/Migrations/MainDemoDbContextModelSnapshot.cs b/WebApi/Migrations/MainDemoDbContextModelSnapshot.cs new file mode 100644 index 0000000..308e689 --- /dev/null +++ b/WebApi/Migrations/MainDemoDbContextModelSnapshot.cs @@ -0,0 +1,53 @@ +// +using System; +using WebApi.MainDbContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace WebApi.Migrations +{ + [DbContext(typeof(MainDemoDbContext))] + partial class MainDemoDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("WebApi.MainDbContext.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("InsertDate") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("Weight") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WebApi/Models/ProductDto.cs b/WebApi/Models/ProductDto.cs new file mode 100644 index 0000000..fa92c4c --- /dev/null +++ b/WebApi/Models/ProductDto.cs @@ -0,0 +1,11 @@ + + +namespace WebApi.Models +{ + public class ProductDto + { + public string Name { get; set; } + public decimal Weight { get; set; } + } + +} \ No newline at end of file diff --git a/WebApi/Program.cs b/WebApi/Program.cs new file mode 100644 index 0000000..64b09ac --- /dev/null +++ b/WebApi/Program.cs @@ -0,0 +1,40 @@ +using WebApi.MainDbContext; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddDbContext( + options => + { + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); + if (!builder.Environment.IsDevelopment()) + { + var hostname = Environment.GetEnvironmentVariable("MSSQL_HOSTNAME"); + var port = Environment.GetEnvironmentVariable("MSSQL_PORT"); + var dbname = Environment.GetEnvironmentVariable("MSSQL_DBNAME"); + var username = Environment.GetEnvironmentVariable("MSSQL_USERNAME"); + var password = Environment.GetEnvironmentVariable("MSSQL_PASSWORD"); + connectionString = string.Format(connectionString, hostname, port, dbname, username, password); + } + options.UseSqlServer(connectionString); + } +); + +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); +app.UseAuthorization(); +app.MapControllers(); + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + +app.Run(); diff --git a/WebApi/Properties/launchSettings.json b/WebApi/Properties/launchSettings.json new file mode 100644 index 0000000..8b57ddd --- /dev/null +++ b/WebApi/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5245" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7219;http://localhost:5245" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64200", + "sslPort": 44326 + } + } +} \ No newline at end of file diff --git a/WebApi/WeatherForecast.cs b/WebApi/WeatherForecast.cs new file mode 100644 index 0000000..7b55bf8 --- /dev/null +++ b/WebApi/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace WebApi +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/WebApi/WebApi.csproj b/WebApi/WebApi.csproj new file mode 100644 index 0000000..065ad36 --- /dev/null +++ b/WebApi/WebApi.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + 5f2bc1ef-f105-43eb-a9ee-89aa706f6d18 + Linux + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/WebApi/WebApi.http b/WebApi/WebApi.http new file mode 100644 index 0000000..592fec5 --- /dev/null +++ b/WebApi/WebApi.http @@ -0,0 +1,6 @@ +@WebApi_HostAddress = http://localhost:5245 + +GET {{WebApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/WebApi/appsettings.Development.json b/WebApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebApi/appsettings.Production.json b/WebApi/appsettings.Production.json new file mode 100644 index 0000000..af29536 --- /dev/null +++ b/WebApi/appsettings.Production.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server={0},{1};Database={2};User={3};Password={4};TrustServerCertificate=True" + } +} diff --git a/WebApi/appsettings.json b/WebApi/appsettings.json new file mode 100644 index 0000000..a4a111d --- /dev/null +++ b/WebApi/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=localhost,1433;Database=MainDemo;User=sa;Password=Password1*;TrustServerCertificate=True" + } +} diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..de41b93 --- /dev/null +++ b/compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +services: + web-api: + container_name: web-api + build: WebApi + environment: + - ASPNETCORE_ENVIRONMENT=Production + - MSSQL_HOSTNAME=sqlserver + - MSSQL_PORT=1433 + - MSSQL_DBNAME=MainDemo + - MSSQL_USERNAME=sa + - MSSQL_PASSWORD=Password1* + ports: + - "8080:8080" + depends_on: + - sqlserver + + sqlserver: + container_name: sqlserver + image: mcr.microsoft.com/mssql/server:2022-latest + platform: linux/amd64 + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=Password1* + ports: + - "1433:1433" + volumes: + - mssql-data:/var/opt/mssql + healthcheck: + test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Password1* -Q 'SELECT 1' || exit 1"] + interval: 10s + retries: 10 + start_period: 10s + timeout: 3s + +volumes: + mssql-data: From 18dbde5e18dffd9bf3d455f28484b23e7b650cae Mon Sep 17 00:00:00 2001 From: Kashif Saadat Date: Wed, 3 Jul 2024 14:37:42 +0100 Subject: [PATCH 2/2] ci: add github ci workflows --- .github/workflows/pr.yml | 26 ++++++++++++++ .github/workflows/release.yml | 64 +++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..3f42b90 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,26 @@ +name: Create and test a Docker image + +on: + pull_request: + branches: ['main'] + +jobs: + build-and-test-image: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Start containers + run: docker compose up --build --detach + + - name: Run test + run: | + docker ps + for i in {1..10}; do curl -m 10 -s -L localhost:8080/swagger | grep 'WebApi' && echo "[Attempt $i] Endpoint returned expected response." && exit 0 || echo "[Attempt $i] Endpoint not returning expected response, retrying in 5 seconds.." && sleep 5; done; exit 1 + + - name: Stop containers + run: docker compose down diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..63abf16 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,64 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `main`, or a version tag is pushed. +on: + push: + branches: ['main'] + tags: + - 'v*' + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: ./app + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true + \ No newline at end of file