source: iknow-api/Program.cs@ 002cf5f

Last change on this file since 002cf5f was 002cf5f, checked in by Boris Gjorgjievski <boris@…>, 7 days ago

Phase 8: connection pooling and concurrent-enrolment handling

Two enrolments for the same semester sent at once both pass the
"already enrolled" check, because each transaction reads the state from
before the other one wrote. UNIQUE (user_id, semester_id) is what
settles it, so EnrollAsync now catches the 23505 and returns the same
sentence instead of a 500.

Sizes the connection pool in the connection string (max 10, min 1)
rather than taking Npgsql's default of 100 - every physical connection
is one more channel through the SSH tunnel - and switches to
AddDbContextPool, which AppDbContext qualifies for since its only
constructor takes DbContextOptions.

docs/Ph8.md is the wiki page: the three transactional scenarios, the
isolation level and what it does not cover, and the pool settings with
the measured connection counts.

  • Property mode set to 100644
File size: 4.3 KB
Line 
1using System.Text;
2using System.Text.Json.Serialization;
3using iknow_api.Data;
4using iknow_api.Models;
5using iknow_api.Repositories;
6using iknow_api.Services;
7using Microsoft.AspNetCore.Authentication.JwtBearer;
8using Microsoft.EntityFrameworkCore;
9using Microsoft.IdentityModel.Tokens;
10
11var builder = WebApplication.CreateBuilder(args);
12
13// Add CORS policy
14builder.Services.AddCors(options =>
15{
16 options.AddPolicy("AllowFrontend", policy =>
17 {
18 var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
19 ?? new[] { "http://localhost:3000" };
20
21 policy.WithOrigins(allowedOrigins) // Your frontend URL(s)
22 .AllowAnyHeader()
23 .AllowAnyMethod()
24 .AllowCredentials();
25 });
26});
27
28builder.Services.AddAuthentication(options =>
29{
30 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
31 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
32})
33.AddJwtBearer(options =>
34{
35 options.RequireHttpsMetadata = false; // only for development
36 options.SaveToken = true;
37 options.TokenValidationParameters = new TokenValidationParameters
38 {
39 ValidateIssuer = true,
40 ValidIssuer = "iknow-api",
41
42 ValidateAudience = true,
43 ValidAudience = "iknow-api",
44
45 ValidateLifetime = true,
46
47 ValidateIssuerSigningKey = true,
48 IssuerSigningKey = new SymmetricSecurityKey(
49 Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
50 };
51});
52// Add services to the container.
53builder.Services.AddControllers()
54 .AddJsonOptions(options =>
55 {
56 options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
57 options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
58 options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
59 });
60builder.Services.AddOpenApi();
61
62// Register DbContext.
63// The database lives behind the SSH tunnel: tunnel_scripta.cmd maps
64// localhost:9999 to the faculty Postgres server. Start it before the API.
65// The enum labels come from sql/ddl.sql, so each CLR enum is mapped onto its
66// Postgres type by name; PgEnumLabels covers the members whose label is not
67// simply the lowercased member name.
68// AddDbContextPool reuses the DbContext instances themselves; the connections
69// underneath them are pooled separately by Npgsql, sized in the connection
70// string. Both matter here because every physical connection is one more
71// channel through the SSH tunnel.
72builder.Services.AddDbContextPool<AppDbContext>(options =>
73 options.UseNpgsql(
74 builder.Configuration.GetConnectionString("DefaultConnection"),
75 npgsql =>
76 {
77 npgsql.MapEnum<UserRole>("user_role", AppDbContext.ProjectSchema, PgEnumLabels.UserRole);
78 npgsql.MapEnum<HighSchoolType>("hs_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase);
79 npgsql.MapEnum<Quota>("quota_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase);
80 npgsql.MapEnum<sType>("semester_type", AppDbContext.ProjectSchema, PgEnumLabels.Lowercase);
81 npgsql.MapEnum<Grade>("grade_type", AppDbContext.ProjectSchema, PgEnumLabels.Grade);
82 }));
83
84// Register your services
85builder.Services.AddScoped<IAuthService, AuthService>();
86builder.Services.AddScoped<IUserRepository, UserRepository>(); // You'll need to add the UserRepository implementation
87builder.Services.AddScoped<IUserService, UserService>();
88
89builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
90builder.Services.AddScoped<IUserRepository, UserRepository>();
91builder.Services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
92builder.Services.AddScoped<IProfRepository, ProfRepository>();
93builder.Services.AddScoped<IProfService, ProfService>();
94builder.Services.AddScoped<IEnrollmentService, EnrollmentService>();
95builder.Services.AddScoped<IAdminService, AdminService>();
96builder.Services.AddAuthorization();
97var app = builder.Build();
98
99// Configure the HTTP request pipeline.
100if (app.Environment.IsDevelopment())
101{
102 app.MapOpenApi();
103}
104
105// Enable CORS - must be called before UseAuthentication and UseAuthorization
106app.UseCors("AllowFrontend");
107
108app.UseHttpsRedirection();
109app.UseAuthentication(); // <-- must come BEFORE UseAuthorization
110app.UseAuthorization();
111app.MapControllers();
112
113app.Run();
Note: See TracBrowser for help on using the repository browser.