Skip to content

Commit aaf8fde

Browse files
authored
Merge pull request #629 from Kaliumhexacyanoferrat/genhttp-crud
[C#] GenHTTP: Implement CRUD
2 parents 3b40382 + bafbd11 commit aaf8fde

11 files changed

Lines changed: 402 additions & 174 deletions

File tree

frameworks/genhttp/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ FROM mcr.microsoft.com/dotnet/runtime:10.0
1111
WORKDIR /app
1212
COPY --from=build /app .
1313

14-
EXPOSE 8080
15-
ENTRYPOINT ["dotnet", "genhttp.dll"]
14+
EXPOSE 8080 8081 8443
15+
ENTRYPOINT ["dotnet", "genhttp.dll"]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Npgsql;
2+
3+
namespace genhttp.Infrastructure;
4+
5+
public static class Postgres
6+
{
7+
private static readonly NpgsqlDataSource? _pool = OpenPool();
8+
9+
public static NpgsqlDataSource? Pool { get => _pool; }
10+
11+
private static NpgsqlDataSource? OpenPool()
12+
{
13+
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
14+
15+
if (string.IsNullOrEmpty(dbUrl)) return null;
16+
17+
try
18+
{
19+
var uri = new Uri(dbUrl);
20+
var userInfo = uri.UserInfo.Split(':');
21+
var maxConn = int.TryParse(Environment.GetEnvironmentVariable("DATABASE_MAX_CONN"), out var p) && p > 0 ? p : 256;
22+
var minConn = Math.Min(64, maxConn);
23+
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo[0]};Password={userInfo[1]};Database={uri.AbsolutePath.TrimStart('/')};Maximum Pool Size={maxConn};Minimum Pool Size={minConn};Multiplexing=true;No Reset On Close=true;Max Auto Prepare=20;Auto Prepare Min Usages=1";
24+
var builder = new NpgsqlDataSourceBuilder(connStr);
25+
26+
return builder.Build();
27+
}
28+
catch
29+
{
30+
return null;
31+
}
32+
}
33+
34+
}

frameworks/genhttp/Model.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace genhttp;
22

3-
public class DatasetItem
3+
public sealed class DatasetItem
44
{
55
public int Id { get; set; }
66
public string Name { get; set; } = "";
@@ -12,7 +12,7 @@ public class DatasetItem
1212
public RatingInfo? Rating { get; set; }
1313
}
1414

15-
public class ProcessedItem
15+
public sealed class ProcessedItem
1616
{
1717
public int Id { get; set; }
1818
public string Name { get; set; } = "";
@@ -25,17 +25,35 @@ public class ProcessedItem
2525
public long Total { get; set; }
2626
}
2727

28-
public class RatingInfo
28+
public sealed class RatingInfo
2929
{
3030
public int Score { get; set; }
3131
public int Count { get; set; }
3232
}
3333

34-
public class ListWithCount<T>(List<T> items)
34+
public sealed class ListWithCount<T>(List<T> items)
3535
{
3636

3737
public List<T> Items => items;
38-
38+
3939
public int Count => items.Count;
4040

41-
}
41+
}
42+
43+
44+
public sealed class CrudListResponse
45+
{
46+
public List<ProcessedItem> Items { get; set; } = [];
47+
public long Total { get; set; }
48+
public int Page { get; set; }
49+
public int Limit { get; set; }
50+
}
51+
52+
public sealed class CrudItem
53+
{
54+
public int? Id { get; set; }
55+
public string? Name { get; set; }
56+
public string? Category { get; set; }
57+
public int Price { get; set; }
58+
public int Quantity { get; set; }
59+
}

frameworks/genhttp/Project.cs

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,52 @@
1-
using GenHTTP.Api.Content;
2-
using GenHTTP.Modules.IO;
3-
using GenHTTP.Modules.Layouting;
4-
using GenHTTP.Modules.Layouting.Provider;
5-
using GenHTTP.Modules.Webservices;
6-
using GenHTTP.Modules.Websockets;
7-
8-
using genhttp.Tests;
9-
10-
namespace genhttp;
11-
12-
public static class Project
13-
{
14-
public static IHandlerBuilder Create()
15-
{
16-
var app = Layout.Create()
17-
.Add("pipeline", Content.From(Resource.FromString("ok")))
18-
.AddService<Baseline>("baseline11")
19-
.AddService<Baseline>("baseline2")
20-
.AddService<Upload>("upload")
21-
.AddService<Json>("json")
22-
.AddService<AsyncDatabase>("async-db")
23-
.AddStaticFiles()
24-
.AddWebsocket();
25-
26-
return app;
27-
}
28-
29-
private static LayoutBuilder AddStaticFiles(this LayoutBuilder app)
30-
{
31-
if (Directory.Exists("/data/static"))
32-
{
33-
app.Add("static", Resources.From(ResourceTree.FromDirectory("/data/static")));
34-
}
35-
36-
return app;
37-
}
38-
39-
private static LayoutBuilder AddWebsocket(this LayoutBuilder app)
40-
{
41-
var websocket = Websocket.Imperative()
42-
.DoNotAllocateFrameData()
43-
.Handler(new EchoHandler());
44-
45-
return app.Add("ws", websocket);
46-
}
47-
48-
}
1+
using GenHTTP.Api.Content;
2+
using GenHTTP.Modules.IO;
3+
using GenHTTP.Modules.Layouting;
4+
using GenHTTP.Modules.Layouting.Provider;
5+
using GenHTTP.Modules.Webservices;
6+
using GenHTTP.Modules.Websockets;
7+
8+
using genhttp.Tests;
9+
10+
namespace genhttp;
11+
12+
public static class Project
13+
{
14+
public static IHandlerBuilder Create()
15+
{
16+
var crud = Layout.Create()
17+
.AddService<Crud>("items");
18+
19+
var app = Layout.Create()
20+
.Add("pipeline", Content.From(Resource.FromString("ok")))
21+
.AddService<Baseline>("baseline11")
22+
.AddService<Baseline>("baseline2")
23+
.AddService<Upload>("upload")
24+
.AddService<Json>("json")
25+
.AddService<AsyncDatabase>("async-db")
26+
.Add("crud", crud)
27+
.AddStaticFiles()
28+
.AddWebsocket();
29+
30+
return app;
31+
}
32+
33+
private static LayoutBuilder AddStaticFiles(this LayoutBuilder app)
34+
{
35+
if (Directory.Exists("/data/static"))
36+
{
37+
app.Add("static", Resources.From(ResourceTree.FromDirectory("/data/static")));
38+
}
39+
40+
return app;
41+
}
42+
43+
private static LayoutBuilder AddWebsocket(this LayoutBuilder app)
44+
{
45+
var websocket = Websocket.Imperative()
46+
.DoNotAllocateFrameData()
47+
.Handler(new EchoHandler());
48+
49+
return app.Add("ws", websocket);
50+
}
51+
52+
}
Lines changed: 49 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,49 @@
1-
using System.Text.Json;
2-
3-
using GenHTTP.Modules.Webservices;
4-
5-
using Npgsql;
6-
7-
namespace genhttp.Tests;
8-
9-
public class AsyncDatabase
10-
{
11-
private static readonly NpgsqlDataSource? PgDataSource = OpenPgPool();
12-
13-
private static NpgsqlDataSource? OpenPgPool()
14-
{
15-
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
16-
if (string.IsNullOrEmpty(dbUrl)) return null;
17-
try
18-
{
19-
var uri = new Uri(dbUrl);
20-
var userInfo = uri.UserInfo.Split(':');
21-
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo[0]};Password={userInfo[1]};Database={uri.AbsolutePath.TrimStart('/')};Maximum Pool Size=256;Minimum Pool Size=64;Multiplexing=true;No Reset On Close=true;Max Auto Prepare=4;Auto Prepare Min Usages=1";
22-
var builder = new NpgsqlDataSourceBuilder(connStr);
23-
return builder.Build();
24-
}
25-
catch { return null; }
26-
}
27-
28-
[ResourceMethod]
29-
public async Task<ListWithCount<object>> Compute(int min = 10, int max = 50, int limit = 50)
30-
{
31-
if (PgDataSource == null)
32-
{
33-
return new ListWithCount<object>(new List<object>());
34-
}
35-
36-
await using var cmd = PgDataSource.CreateCommand(
37-
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3");
38-
39-
cmd.Parameters.AddWithValue(min);
40-
cmd.Parameters.AddWithValue(max);
41-
cmd.Parameters.AddWithValue(limit);
42-
43-
await using var reader = await cmd.ExecuteReaderAsync();
44-
45-
var items = new List<object>(limit);
46-
47-
while (await reader.ReadAsync())
48-
{
49-
items.Add(new
50-
{
51-
id = reader.GetInt32(0),
52-
name = reader.GetString(1),
53-
category = reader.GetString(2),
54-
price = reader.GetInt32(3),
55-
quantity = reader.GetInt32(4),
56-
active = reader.GetBoolean(5),
57-
tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
58-
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
59-
});
60-
}
61-
62-
return new ListWithCount<object>(items);
63-
}
64-
65-
}
1+
using System.Text.Json;
2+
using genhttp.Infrastructure;
3+
using GenHTTP.Modules.Webservices;
4+
5+
namespace genhttp.Tests;
6+
7+
public class AsyncDatabase
8+
{
9+
10+
[ResourceMethod]
11+
public async Task<ListWithCount<object>> Compute(int min = 10, int max = 50, int limit = 50)
12+
{
13+
var pool = Postgres.Pool;
14+
15+
if (pool == null)
16+
{
17+
return new ListWithCount<object>(new List<object>());
18+
}
19+
20+
await using var cmd = pool.CreateCommand(
21+
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3");
22+
23+
cmd.Parameters.AddWithValue(min);
24+
cmd.Parameters.AddWithValue(max);
25+
cmd.Parameters.AddWithValue(limit);
26+
27+
await using var reader = await cmd.ExecuteReaderAsync();
28+
29+
var items = new List<object>(limit);
30+
31+
while (await reader.ReadAsync())
32+
{
33+
items.Add(new
34+
{
35+
id = reader.GetInt32(0),
36+
name = reader.GetString(1),
37+
category = reader.GetString(2),
38+
price = reader.GetInt32(3),
39+
quantity = reader.GetInt32(4),
40+
active = reader.GetBoolean(5),
41+
tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
42+
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
43+
});
44+
}
45+
46+
return new ListWithCount<object>(items);
47+
}
48+
49+
}

0 commit comments

Comments
 (0)