2
0

Initial exercise setup.

Additions

* Instructions for manually starting the database.
* Each phase now has a success criteria with how to verify.
* Troubleshooting section for working with the Docker DB container.
* All phases of the exercise are described in README.md (with as little advanced hints as possible).
* DFD to illustrate phase 1
* Debugging starts the database container and applies schema migrations (no setup required).
* Launch settings for VS Code, VS 2022 and Rider (IntelliJ IDEA).
* Swagger UI is available at <http://localhost:8080/swagger/index.html> for testing.
This commit is contained in:
2025-06-26 11:32:01 +02:00
parent 5a6009b76d
commit cf91281a51
21 changed files with 504 additions and 0 deletions

10
service/AppDbContext.cs Normal file
View File

@@ -0,0 +1,10 @@
using Microsoft.EntityFrameworkCore;
namespace service;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Post> Posts { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
namespace service;
public class JsonPlaceholderClient
{
private readonly HttpClient _client;
public JsonPlaceholderClient(HttpClient client)
{
_client = client;
// TODO: Configure the client as needed.
}
public async Task<PostModel?> GetPostByIdAsync(int id, CancellationToken ct = default)
{
// TODO: Implement the logic to call the external service and retrieve the post by ID.
throw new NotImplementedException("This method will be implemented in Phase 1.");
}
}

27
service/Post.cs Normal file
View File

@@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace service;
[Table("posts")]
public class Post
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("user_id")]
public int UserId { get; set; }
[Column("title")]
[Required]
public string Title { get; set; }
[Column("body")]
[Required]
public string Body { get; set; }
[Column("updated_at")]
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.Parse("1970-01-01T00:00:00Z"); // TODO: Remove the default value in Phase 3.
}

12
service/PostModel.cs Normal file
View File

@@ -0,0 +1,12 @@
using System;
namespace service;
[Serializable]
public class PostModel
{
public int Id { get; set; }
public int UserId { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}

46
service/Program.cs Normal file
View File

@@ -0,0 +1,46 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Polly.Simmy;
using service;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var services = builder.Services;
services.AddRequestTimeouts();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddDbContext<AppDbContext>(optionsBuilder =>
{
// Configure the database connection string.
var connectionString = builder.Configuration.GetValue<string>("PostgresConnection");
Console.WriteLine($"Connecting to PostgreSQL database with connection string: {connectionString}");
optionsBuilder.UseNpgsql(connectionString);
});
var httpClientBuilder = services.AddHttpClient<JsonPlaceholderClient>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(); // Swagger UI is available at http://localhost:8080/swagger/index.html
app.UseRequestTimeouts();
app.MapGet("/posts/{id}", async (AppDbContext dbContext, JsonPlaceholderClient client, int id) =>
{
// TODO: (Phase 1) Implement the logic to retrieve a post by ID and store it in the database.
// Consider some minimal error handling in case the post is not found (e.g. Id > 100).
return Results.Ok();
})
.WithRequestTimeout(TimeSpan.FromSeconds(29))
.WithName("GetPostById")
.WithOpenApi();
app.Run();

View File

@@ -0,0 +1,14 @@
{
"profiles": {
"service": {
"commandName": "Project",
"launchBrowser": true,
"useSSL": false,
"launchUrl": "http://localhost:8080/swagger/index.html",
"applicationUrl": "http://localhost:8080/swagger/index.html",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
}
}
}

View File

@@ -0,0 +1,3 @@
{
"PostgresConnection": "Server=db;Database=mydatabase;User Id=user;Password=password;"
}

11
service/appsettings.json Normal file
View File

@@ -0,0 +1,11 @@
{
"Urls": "http://0.0.0.0:8080",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"PostgresConnection": "Server=localhost;Database=mydatabase;User Id=user;Password=password;"
}

20
service/service.csproj Normal file
View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.16" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.6.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="docker compose up -d db" />
</Target>
</Project>