From 73fb63b3813b3fc065da62a6b22b49af4139cac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20N=C3=B8rgaard?= Date: Fri, 21 Jun 2024 00:11:21 +0200 Subject: [PATCH] Minor progress adding another (users) service --- .env | 24 ++++++++-- .github/workflows/users.yml | 44 ++++++++++++++++++ compose.yml | 25 ++++++++++ dockerfiles/users.Dockerfile | 18 ++++++++ .../ServiceCollectionExtension.cs | 28 ++++++++++- src/Ast.Todos/Startup.cs | 2 +- src/Ast.Todos/appsettings.json | 2 +- src/Ast.Users/Ast.Users.csproj | 23 ++++++++++ .../Configurations/UserConfiguration.cs | 14 ++++++ .../Database/Configurations/UserConstants.cs | 10 ++++ .../20240620214244_Initial.Designer.cs | 46 +++++++++++++++++++ .../Migrations/20240620214244_Initial.cs | 34 ++++++++++++++ .../Migrations/UserContextModelSnapshot.cs | 43 +++++++++++++++++ src/Ast.Users/Database/Models/User.cs | 7 +++ src/Ast.Users/Database/UserContext.cs | 19 ++++++++ src/Ast.Users/Program.cs | 31 +++++++++++++ src/Ast.Users/Properties/launchSettings.json | 14 ++++++ src/Ast.Users/appsettings.Development.json | 2 + src/Ast.Users/appsettings.json | 15 ++++++ src/Ast.sln | 6 +++ 20 files changed, 399 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/users.yml create mode 100644 dockerfiles/users.Dockerfile create mode 100644 src/Ast.Users/Ast.Users.csproj create mode 100644 src/Ast.Users/Database/Configurations/UserConfiguration.cs create mode 100644 src/Ast.Users/Database/Configurations/UserConstants.cs create mode 100644 src/Ast.Users/Database/Migrations/20240620214244_Initial.Designer.cs create mode 100644 src/Ast.Users/Database/Migrations/20240620214244_Initial.cs create mode 100644 src/Ast.Users/Database/Migrations/UserContextModelSnapshot.cs create mode 100644 src/Ast.Users/Database/Models/User.cs create mode 100644 src/Ast.Users/Database/UserContext.cs create mode 100644 src/Ast.Users/Program.cs create mode 100644 src/Ast.Users/Properties/launchSettings.json create mode 100644 src/Ast.Users/appsettings.Development.json create mode 100644 src/Ast.Users/appsettings.json diff --git a/.env b/.env index cdda74e..c871d41 100644 --- a/.env +++ b/.env @@ -1,13 +1,27 @@ # Postgres POSTGRES_PASSWORD=mysecretpassword -POSTGRES_USER=todos -POSTGRES_DB=todos +POSTGRES_USER=postgres +POSTGRES_DB=postgres POSTGRES_HOST=localhost POSTGRES_PORT=5432 +# Open Telemetry Collector +OTEL_COLLECTOR_HOST="http://otel-collector:4317" + +# Services +DATABASE_HOST="postgres" +ENVIRONMENT_NAME="Docker" + # Todos API -ASPNETCORE_ENVIRONMENT="Docker" +ASPNETCORE_ENVIRONMENT=${ENVIRONMENT_NAME} ServiceOptions__ServiceName="Todos API" -ServiceOptions__ConnectionString="Host=postgres; Database=todos; Username=todos; Password=mysecretpassword" +ServiceOptions__ConnectionString="Host=${DATABASE_HOST}; Database=todos; Username=${POSTGRES_USER}; Password=${POSTGRES_PASSWORD}" +ServiceOptions__HangfireEnabled="true" +ServiceOptions__TelemetryCollectorHost=${OTEL_COLLECTOR_HOST} + +# Users API +ASPNETCORE_ENVIRONMENT=${ENVIRONMENT_NAME} +ServiceOptions__ServiceName="Users API" +ServiceOptions__ConnectionString="Host=postgres; Database=users; Username=${POSTGRES_USER}; Password=${POSTGRES_PASSWORD}" ServiceOptions__HangfireEnabled="true" -ServiceOptions__TelemetryCollectorHost="http://otel-collector:4317" +ServiceOptions__TelemetryCollectorHost=${OTEL_COLLECTOR_HOST} diff --git a/.github/workflows/users.yml b/.github/workflows/users.yml new file mode 100644 index 0000000..46aa962 --- /dev/null +++ b/.github/workflows/users.yml @@ -0,0 +1,44 @@ +name: Build Users Api + +on: + push: + branches: + - "main" + paths: + - ".github/workflows/users.yml" + - "dockerfiles/users.Dockerfile" + - "src/**" + pull_request: + branches: + - "main" + paths: + - ".github/workflows/users.yml" + - "dockerfiles/users.Dockerfile" + - "src/**" + +jobs: + dotnet-test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./src/ + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal + + docker-build: + runs-on: ubuntu-latest + needs: dotnet-test + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: docker build -f dockerfiles/users.Dockerfile src diff --git a/compose.yml b/compose.yml index ec4ce26..38c7e52 100644 --- a/compose.yml +++ b/compose.yml @@ -9,6 +9,31 @@ services: - "6001:8080" env_file: - .env + environment: + ASPNETCORE_ENVIRONMENT: ${ENVIRONMENT_NAME} + ServiceOptions__ServiceName: "Todos API" + ServiceOptions__ConnectionString: "Host=${DATABASE_HOST}; Database=todos; Username=${POSTGRES_USER}; Password=${POSTGRES_PASSWORD}" + ServiceOptions__HangfireEnabled: "true" + ServiceOptions__TelemetryCollectorHost: ${OTEL_COLLECTOR_HOST} + restart: on-failure + depends_on: + - postgres + + users-api: + image: ursu/ant-users-api:latest + build: + context: src + dockerfile: ../dockerfiles/users.Dockerfile + ports: + - "6002:8080" + env_file: + - .env + environment: + ASPNETCORE_ENVIRONMENT: ${ENVIRONMENT_NAME} + ServiceOptions__ServiceName: "Users API" + ServiceOptions__ConnectionString: "Host=postgres; Database=users; Username=${POSTGRES_USER}; Password=${POSTGRES_PASSWORD}" + ServiceOptions__HangfireEnabled: "true" + ServiceOptions__TelemetryCollectorHost: ${OTEL_COLLECTOR_HOST} restart: on-failure depends_on: - postgres diff --git a/dockerfiles/users.Dockerfile b/dockerfiles/users.Dockerfile new file mode 100644 index 0000000..22dfff8 --- /dev/null +++ b/dockerfiles/users.Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app + +COPY *.sln ./ +COPY */*.csproj ./ + +RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done +RUN dotnet restore + +COPY . . +RUN dotnet build +RUN dotnet publish Ast.Users/Ast.Users.csproj -c Release -o out + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +WORKDIR /app + +COPY --from=build /app/out . +ENTRYPOINT [ "dotnet", "Ast.Users.dll" ] diff --git a/src/Ast.Platform/ServiceCollectionExtension.cs b/src/Ast.Platform/ServiceCollectionExtension.cs index 1a4b8db..c4f4399 100644 --- a/src/Ast.Platform/ServiceCollectionExtension.cs +++ b/src/Ast.Platform/ServiceCollectionExtension.cs @@ -28,6 +28,20 @@ public static void AddPlatformServices( services.AddPlatformHangfire(configuration); } + public static WebApplicationBuilder AddPlatformServices(this WebApplicationBuilder builder) + { + var configuration = builder.Configuration; + configuration.ValidatePlatformConfiguration(); + builder.Services.AddPlatformTelemetry(configuration); + builder.Services.AddPlatformHangfire(configuration); + builder.Services.AddCorsPolicy(configuration); + builder.Services.AddHealthChecks(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + return builder; + } + public static void UsePlatformServices(this IApplicationBuilder app, IConfiguration configuration) { app.UsePlatformTelemetry(configuration); @@ -41,4 +55,16 @@ public static void UsePlatformServices(this IApplicationBuilder app, IConfigurat endpoints.EnabledHangfireDashboard(configuration); }); } -} \ No newline at end of file + + public static WebApplication MapPlatformServices(this WebApplication app) + { + var configuration = app.Configuration; + app.UsePlatformTelemetry(configuration); + app.UseCorsPolicy(); + app.MapHealthChecks("/hc"); + app.EnabledHangfireDashboard(configuration); + app.UseSwagger(); + app.UseSwaggerUI(); + return app; + } +} diff --git a/src/Ast.Todos/Startup.cs b/src/Ast.Todos/Startup.cs index da9c5d2..75ea4e8 100644 --- a/src/Ast.Todos/Startup.cs +++ b/src/Ast.Todos/Startup.cs @@ -27,4 +27,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TodoCont todoContext.Database.Migrate(); app.UsePlatformServices(Configuration); } -} \ No newline at end of file +} diff --git a/src/Ast.Todos/appsettings.json b/src/Ast.Todos/appsettings.json index fdd4a27..cfbef5e 100644 --- a/src/Ast.Todos/appsettings.json +++ b/src/Ast.Todos/appsettings.json @@ -8,7 +8,7 @@ "AllowedOrigins": "*", "ServiceOptions": { "ServiceName": "Todos API", - "ConnectionString": "Host=localhost; Database=todos; Username=todos; Password=mysecretpassword", + "ConnectionString": "Host=localhost; Database=todos; Username=postgres; Password=mysecretpassword", "HangfireEnabled": true, "TelemetryCollectorHost": "http://localhost:4317" } diff --git a/src/Ast.Users/Ast.Users.csproj b/src/Ast.Users/Ast.Users.csproj new file mode 100644 index 0000000..4dd4a7b --- /dev/null +++ b/src/Ast.Users/Ast.Users.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/Ast.Users/Database/Configurations/UserConfiguration.cs b/src/Ast.Users/Database/Configurations/UserConfiguration.cs new file mode 100644 index 0000000..0dd64d8 --- /dev/null +++ b/src/Ast.Users/Database/Configurations/UserConfiguration.cs @@ -0,0 +1,14 @@ +using Ast.Users.Database.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Ast.Users.Database.Configurations; + +public class UserConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder todo) + { + todo.HasKey(t => t.Id); + todo.Property(t => t.Name).HasMaxLength(UserConstants.Name.MaxLength).IsRequired(); + } +} diff --git a/src/Ast.Users/Database/Configurations/UserConstants.cs b/src/Ast.Users/Database/Configurations/UserConstants.cs new file mode 100644 index 0000000..4f3ca8e --- /dev/null +++ b/src/Ast.Users/Database/Configurations/UserConstants.cs @@ -0,0 +1,10 @@ +namespace Ast.Users.Database.Configurations; + +public static class UserConstants +{ + public static class Name + { + public const int MaxLength = 250; + public const int MinLength = 1; + } +} diff --git a/src/Ast.Users/Database/Migrations/20240620214244_Initial.Designer.cs b/src/Ast.Users/Database/Migrations/20240620214244_Initial.Designer.cs new file mode 100644 index 0000000..baa374c --- /dev/null +++ b/src/Ast.Users/Database/Migrations/20240620214244_Initial.Designer.cs @@ -0,0 +1,46 @@ +// +using System; +using Ast.Users.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Ast.Users.Database.Migrations +{ + [DbContext(typeof(UserContext))] + [Migration("20240620214244_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Ast.Users.Database.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ast.Users/Database/Migrations/20240620214244_Initial.cs b/src/Ast.Users/Database/Migrations/20240620214244_Initial.cs new file mode 100644 index 0000000..36d5144 --- /dev/null +++ b/src/Ast.Users/Database/Migrations/20240620214244_Initial.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ast.Users.Database.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Ast.Users/Database/Migrations/UserContextModelSnapshot.cs b/src/Ast.Users/Database/Migrations/UserContextModelSnapshot.cs new file mode 100644 index 0000000..48d88e0 --- /dev/null +++ b/src/Ast.Users/Database/Migrations/UserContextModelSnapshot.cs @@ -0,0 +1,43 @@ +// +using System; +using Ast.Users.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Ast.Users.Database.Migrations +{ + [DbContext(typeof(UserContext))] + partial class UserContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Ast.Users.Database.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ast.Users/Database/Models/User.cs b/src/Ast.Users/Database/Models/User.cs new file mode 100644 index 0000000..143ab25 --- /dev/null +++ b/src/Ast.Users/Database/Models/User.cs @@ -0,0 +1,7 @@ +namespace Ast.Users.Database.Models; + +public class User +{ + public Guid Id { get; set; } + public string Name { get; set; } +} diff --git a/src/Ast.Users/Database/UserContext.cs b/src/Ast.Users/Database/UserContext.cs new file mode 100644 index 0000000..2113db2 --- /dev/null +++ b/src/Ast.Users/Database/UserContext.cs @@ -0,0 +1,19 @@ +using Ast.Users.Database.Models; +using Microsoft.EntityFrameworkCore; + +namespace Ast.Users.Database; + +public class UserContext : DbContext +{ + public DbSet Users { get; init; } + + public UserContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.ApplyConfigurationsFromAssembly(typeof(UserContext).Assembly); + } +} diff --git a/src/Ast.Users/Program.cs b/src/Ast.Users/Program.cs new file mode 100644 index 0000000..df8fdc3 --- /dev/null +++ b/src/Ast.Users/Program.cs @@ -0,0 +1,31 @@ +using Ast.Platform; +using Ast.Platform.Options; +using Ast.Users.Database; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); +builder.AddPlatformServices(); +var cs = new ServiceOptions(builder.Configuration).ConnectionString; +builder.Services.AddDbContext(o => o.UseNpgsql(cs)); + +var app = builder.Build(); +var context = app.Services.GetService(); +context?.Database.Migrate(); +app.MapPlatformServices(); + +// app.MapGet("/weatherforecast", () => +// { +// var forecast = Enumerable.Range(1, 5).Select(index => +// new WeatherForecast +// ( +// DateOnly.FromDateTime(DateTime.Now.AddDays(index)), +// Random.Shared.Next(-20, 55), +// summaries[Random.Shared.Next(summaries.Length)] +// )) +// .ToArray(); +// return forecast; +// }) +// .WithName("GetWeatherForecast") +// .WithOpenApi(); + +app.Run(); diff --git a/src/Ast.Users/Properties/launchSettings.json b/src/Ast.Users/Properties/launchSettings.json new file mode 100644 index 0000000..7c902a5 --- /dev/null +++ b/src/Ast.Users/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5002", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Ast.Users/appsettings.Development.json b/src/Ast.Users/appsettings.Development.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/src/Ast.Users/appsettings.Development.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/Ast.Users/appsettings.json b/src/Ast.Users/appsettings.json new file mode 100644 index 0000000..2035dfc --- /dev/null +++ b/src/Ast.Users/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedOrigins": "*", + "ServiceOptions": { + "ServiceName": "Users API", + "ConnectionString": "Host=localhost; Database=users; Username=postgres; Password=mysecretpassword", + "HangfireEnabled": true, + "TelemetryCollectorHost": "http://localhost:4317" + } +} diff --git a/src/Ast.sln b/src/Ast.sln index 506f894..44b63c5 100644 --- a/src/Ast.sln +++ b/src/Ast.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ast.Todos.Tests", "Ast.Todo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ast.Todos", "Ast.Todos\Ast.Todos.csproj", "{4E51E389-4545-4DFE-BCCE-7FF8505955E1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ast.Users", "Ast.Users\Ast.Users.csproj", "{B81AD550-0EBF-440A-8279-56A476ECF509}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {4E51E389-4545-4DFE-BCCE-7FF8505955E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E51E389-4545-4DFE-BCCE-7FF8505955E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E51E389-4545-4DFE-BCCE-7FF8505955E1}.Release|Any CPU.Build.0 = Release|Any CPU + {B81AD550-0EBF-440A-8279-56A476ECF509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B81AD550-0EBF-440A-8279-56A476ECF509}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B81AD550-0EBF-440A-8279-56A476ECF509}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B81AD550-0EBF-440A-8279-56A476ECF509}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal