1+ using Microsoft . AspNetCore . Mvc ;
2+ using Npgsql ;
3+ #if ! EXTRAOPTIMIZE
4+ using OpenTelemetry . Metrics ;
5+ using OpenTelemetry . ResourceDetectors . Container ;
6+ using OpenTelemetry . ResourceDetectors . Host ;
7+ using OpenTelemetry . Resources ;
8+ using OpenTelemetry . Trace ;
9+ #endif
10+ using System . Text . Json ;
11+ using System . Text . Json . Serialization ;
12+
13+ var builder = WebApplication . CreateSlimBuilder ( args ) ;
14+
15+ builder . Services . ConfigureHttpJsonOptions ( options =>
16+ {
17+ options . SerializerOptions . PropertyNamingPolicy = JsonNamingPolicy . SnakeCaseLower ;
18+ options . SerializerOptions . TypeInfoResolverChain . Insert ( 0 , SourceGenerationContext . Default ) ;
19+ } ) ;
20+
21+ #if ! EXTRAOPTIMIZE
22+ Action < ResourceBuilder > appResourceBuilder =
23+ resource => resource
24+ . AddDetector ( new ContainerResourceDetector ( ) )
25+ . AddDetector ( new HostDetector ( ) ) ;
26+
27+ builder . Services . AddOpenTelemetry ( )
28+ . ConfigureResource ( appResourceBuilder )
29+ . WithTracing ( tracerBuilder => tracerBuilder
30+ . AddAspNetCoreInstrumentation ( )
31+ . AddHttpClientInstrumentation ( ) )
32+ . WithMetrics ( meterBuilder => meterBuilder
33+ . AddProcessInstrumentation ( )
34+ . AddRuntimeInstrumentation ( )
35+ . AddAspNetCoreInstrumentation ( )
36+ . AddPrometheusExporter ( ) ) ;
37+ #endif
38+
39+ builder . Services . AddNpgsqlDataSource (
40+ builder . Configuration . GetConnectionString ( "DefaultConnection" ) !
41+ ) ;
42+
43+ var app = builder . Build ( ) ;
44+
45+ #if ! EXTRAOPTIMIZE
46+ app . MapPrometheusScrapingEndpoint ( ) ;
47+ #endif
48+
49+ var clientes = new Dictionary < int , int >
50+ {
51+ { 1 , 100000 } ,
52+ { 2 , 80000 } ,
53+ { 3 , 1000000 } ,
54+ { 4 , 10000000 } ,
55+ { 5 , 500000 }
56+ } ;
57+
58+ #if ! EXTRAOPTIMIZE
59+ app . MapHealthChecks ( "/healthz" ) ;
60+ #endif
61+
62+ app . MapGet ( "/clientes/{id:int}/extrato" , async ( int id , [ FromServices ] NpgsqlDataSource dataSource ) =>
63+ {
64+ if ( ! clientes . TryGetValue ( id , out _ ) )
65+ return Results . NotFound ( ) ;
66+
67+ await using ( var cmd = dataSource . CreateCommand ( ) )
68+ {
69+ cmd . CommandText = "SELECT * FROM GetSaldoClienteById($1)" ;
70+ cmd . Parameters . AddWithValue ( id ) ;
71+
72+ using var reader = await cmd . ExecuteReaderAsync ( ) ;
73+
74+ if ( ! await reader . ReadAsync ( ) )
75+ return Results . NotFound ( ) ;
76+
77+ var saldo = new SaldoDto ( reader . GetInt32 ( 0 ) , reader . GetInt32 ( 1 ) , reader . GetDateTime ( 2 ) ) ;
78+ var jsonDoc = reader . GetFieldValue < JsonDocument > ( 3 ) ;
79+ var ultimasTransacoes = JsonSerializer . Deserialize < List < TransacaoDto > > ( jsonDoc . RootElement , SourceGenerationContext . Default . ListTransacaoDto . Options ) ;
80+
81+ var extrato = new ExtratoDto ( saldo , ultimasTransacoes ) ;
82+
83+ return Results . Ok ( extrato ) ;
84+ }
85+ } ) ;
86+
87+ app . MapPost ( "/clientes/{id:int}/transacoes" , async ( int id , [ FromBody ] TransacaoDto transacao , [ FromServices ] NpgsqlDataSource dataSource ) =>
88+ {
89+ if ( ! clientes . TryGetValue ( id , out int limite ) )
90+ return Results . NotFound ( ) ;
91+
92+ if ( ! IsTransacaoValid ( transacao ) )
93+ return Results . UnprocessableEntity ( ) ;
94+
95+ await using ( var cmd = dataSource . CreateCommand ( ) )
96+ {
97+ cmd . CommandText = "SELECT InsertTransacao($1, $2, $3, $4)" ;
98+ cmd . Parameters . AddWithValue ( id ) ;
99+ cmd . Parameters . AddWithValue ( transacao . Valor ) ;
100+ cmd . Parameters . AddWithValue ( transacao . Tipo ) ;
101+ cmd . Parameters . AddWithValue ( transacao . Descricao ) ;
102+
103+ using var reader = await cmd . ExecuteReaderAsync ( ) ;
104+
105+ if ( ! await reader . ReadAsync ( ) )
106+ return Results . UnprocessableEntity ( ) ;
107+
108+ var updatedSaldo = reader . GetInt32 ( 0 ) ;
109+
110+ return Results . Ok ( new ClienteDto ( id , limite , updatedSaldo ) ) ;
111+ }
112+ } ) ;
113+
114+ app . Run ( ) ;
115+
116+ static bool IsTransacaoValid ( TransacaoDto transacao )
117+ {
118+ ReadOnlySpan < char > tipoC = "c" ;
119+ ReadOnlySpan < char > tipoD = "d" ;
120+
121+ return ( transacao . Tipo . AsSpan ( ) . SequenceEqual ( tipoC ) || transacao . Tipo . AsSpan ( ) . SequenceEqual ( tipoD ) )
122+ && ! string . IsNullOrEmpty ( transacao . Descricao )
123+ && transacao . Descricao . Length <= 10
124+ && transacao . Valor > 0 ;
125+ }
126+
127+ [ JsonSerializable ( typeof ( ClienteDto ) ) ]
128+ [ JsonSerializable ( typeof ( ExtratoDto ) ) ]
129+ [ JsonSerializable ( typeof ( SaldoDto ) ) ]
130+ [ JsonSerializable ( typeof ( TransacaoDto ) ) ]
131+ [ JsonSerializable ( typeof ( List < TransacaoDto > ) ) ]
132+ internal partial class SourceGenerationContext : JsonSerializerContext { }
133+
134+ internal readonly record struct ClienteDto ( int Id , int Limite , int Saldo ) ;
135+ internal readonly record struct ExtratoDto ( SaldoDto Saldo , List < TransacaoDto > ? ultimas_transacoes ) ;
136+ internal readonly record struct SaldoDto ( int Total , int Limite , DateTime data_extrato ) ;
137+ internal readonly record struct TransacaoDto ( int Valor , string Tipo , string Descricao ) ;
0 commit comments