66using sparkly_server . Services . Auth ;
77using sparkly_server . Services . Users ;
88using sparkly_server . Services . UserServices ;
9- using System . Text ;
10- using Scalar . AspNetCore ;
119using sparkly_server . Services . Projects ;
10+ using Scalar . AspNetCore ;
11+ using System . Text ;
12+
13+ var builder = WebApplication . CreateBuilder ( args ) ;
1214
13- namespace sparkly_server ;
15+ // JWT configuration
16+ var jwtKey = builder . Configuration [ "SPARKLY_JWT_KEY" ]
17+ ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_KEY" ) ;
1418
15- public class Program
19+ if ( string . IsNullOrWhiteSpace ( jwtKey ) )
1620{
17- public static void Main ( string [ ] args )
21+ if ( builder . Environment . IsDevelopment ( ) || builder . Environment . IsEnvironment ( "Testing" ) )
1822 {
19- var builder = WebApplication . CreateBuilder ( args ) ;
20-
21- var jwtKey = builder . Configuration [ "SPARKLY_JWT_KEY" ]
22- ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_KEY" ) ;
23+ // Dev / Testing fallback key only for local usage
24+ jwtKey = "dev-only-jwt-key-change-me" ;
25+ }
26+ else
27+ {
28+ throw new Exception ( "JWT key missing" ) ;
29+ }
30+ }
2331
24- if ( string . IsNullOrWhiteSpace ( jwtKey ) )
25- {
26- if ( builder . Environment . IsDevelopment ( ) )
27- {
28- jwtKey = "dev-only-jwt-key-change-me" ;
29- }
30- else
31- {
32- throw new Exception ( "JWT key missing" ) ;
33- }
34- }
35-
36-
37- var jwtIssuer = builder . Configuration [ "SPARKLY_JWT_ISSUER" ]
38- ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_ISSUER" )
39- ?? "sparkly" ;
40-
41- var jwtAudience = builder . Configuration [ "SPARKLY_JWT_AUDIENCE" ]
42- ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_AUDIENCE" )
43- ?? "sparkly-api" ;
44-
45- builder . Services . AddHttpContextAccessor ( ) ;
46-
47- builder . Services . AddAuthorization ( options =>
48- {
49- options . AddPolicy ( "AdminOnly" , policy =>
50- policy . RequireRole ( Roles . Admin ) ) ;
51- } ) ;
52-
53- // Services
54- builder . Services . AddScoped < IUserRepository , UserRepository > ( ) ;
55- builder . Services . AddScoped < IUserService , UserService > ( ) ;
56- builder . Services . AddScoped < IJwtProvider , JwtProvider > ( ) ;
57- builder . Services . AddScoped < IAuthService , AuthService > ( ) ;
58- builder . Services . AddScoped < ICurrentUser , CurrentUser > ( ) ;
59- builder . Services . AddScoped < IProjectRepository , ProjectRepository > ( ) ;
60- builder . Services . AddScoped < IProjectService , ProjectService > ( ) ;
61-
62- var connectionString = builder . Configuration . GetConnectionString ( "Default" )
63- ?? Environment . GetEnvironmentVariable ( "ConnectionStrings__Default" )
64- ?? throw new Exception ( "Connection string 'Default' not found." ) ;
65-
66- builder . Services . AddDbContext < AppDbContext > ( options =>
67- {
68- options . UseNpgsql ( connectionString ) ;
69- } ) ;
32+ var jwtIssuer = builder . Configuration [ "SPARKLY_JWT_ISSUER" ]
33+ ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_ISSUER" )
34+ ?? "sparkly" ;
7035
71- builder . Services . AddControllers ( ) ;
36+ var jwtAudience = builder . Configuration [ "SPARKLY_JWT_AUDIENCE" ]
37+ ?? Environment . GetEnvironmentVariable ( "SPARKLY_JWT_AUDIENCE" )
38+ ?? "sparkly-api" ;
7239
73- builder . Services . AddOpenApi ( ) ;
74-
75- builder . Services . AddCors ( options =>
76- {
77- options . AddPolicy ( "FrontendDev" , policy =>
78- {
79- policy
80- . WithOrigins ( "http://localhost:4200" )
81- . AllowAnyHeader ( )
82- . AllowAnyMethod ( )
83- . AllowCredentials ( ) ;
84- } ) ;
85- } ) ;
86-
87- builder . Services
88- . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
89- . AddJwtBearer ( options =>
90- {
91- options . TokenValidationParameters = new TokenValidationParameters
92- {
93- ValidateIssuer = false ,
94- ValidateAudience = false ,
95- ValidateLifetime = true ,
96- IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( jwtKey ) ) ,
97- ValidateIssuerSigningKey = true ,
98- ClockSkew = TimeSpan . FromMinutes ( 1 ) ,
99- } ;
100- } ) ;
101-
102- var app = builder . Build ( ) ;
103-
104- // Configure the HTTP request pipeline.
105- if ( app . Environment . IsDevelopment ( ) )
106- {
107- app . MapOpenApi ( ) ;
108-
109- app . MapScalarApiReference ( ) ;
110- }
40+ // Common services
41+ builder . Services . AddHttpContextAccessor ( ) ;
42+
43+ builder . Services . AddAuthorization ( options =>
44+ {
45+ options . AddPolicy ( "AdminOnly" , policy =>
46+ policy . RequireRole ( Roles . Admin ) ) ;
47+ } ) ;
48+
49+ // Domain / app services
50+ builder . Services . AddScoped < IUserRepository , UserRepository > ( ) ;
51+ builder . Services . AddScoped < IUserService , UserService > ( ) ;
52+ builder . Services . AddScoped < IJwtProvider , JwtProvider > ( ) ;
53+ builder . Services . AddScoped < IAuthService , AuthService > ( ) ;
54+ builder . Services . AddScoped < ICurrentUser , CurrentUser > ( ) ;
55+ builder . Services . AddScoped < IProjectRepository , ProjectRepository > ( ) ;
56+ builder . Services . AddScoped < IProjectService , ProjectService > ( ) ;
57+
58+ // Database
59+ if ( ! builder . Environment . IsEnvironment ( "Testing" ) )
60+ {
61+ var connectionString = builder . Configuration . GetConnectionString ( "Default" )
62+ ?? Environment . GetEnvironmentVariable ( "ConnectionStrings__Default" )
63+ ?? throw new Exception ( "Connection string 'Default' not found." ) ;
11164
112- app . UseHttpsRedirection ( ) ;
65+ builder . Services . AddDbContext < AppDbContext > ( options =>
66+ {
67+ options . UseNpgsql ( connectionString ) ;
68+ } ) ;
69+ }
11370
114- app . UseAuthentication ( ) ;
115- app . UseAuthorization ( ) ;
116-
117- app . UseCors ( "FrontendDev" ) ;
71+ builder . Services . AddControllers ( ) ;
11872
119- app . MapControllers ( ) ;
73+ // OpenAPI / Scalar
74+ builder . Services . AddOpenApi ( ) ;
12075
121- using ( var scope = app . Services . CreateScope ( ) )
76+ // CORS
77+ builder . Services . AddCors ( options =>
78+ {
79+ options . AddPolicy ( "FrontendDev" , policy =>
80+ {
81+ policy
82+ . WithOrigins ( "http://localhost:4200" )
83+ . AllowAnyHeader ( )
84+ . AllowAnyMethod ( )
85+ . AllowCredentials ( ) ;
86+ } ) ;
87+ } ) ;
88+
89+ // Authentication
90+ builder . Services
91+ . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
92+ . AddJwtBearer ( options =>
93+ {
94+ options . TokenValidationParameters = new TokenValidationParameters
12295 {
123- var db = scope . ServiceProvider . GetRequiredService < AppDbContext > ( ) ;
124- db . Database . Migrate ( ) ;
125- }
126-
127- app . Run ( ) ;
128- }
96+ ValidateIssuer = false ,
97+ ValidateAudience = false ,
98+ ValidateLifetime = true ,
99+ IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( jwtKey ) ) ,
100+ ValidateIssuerSigningKey = true ,
101+ ClockSkew = TimeSpan . FromMinutes ( 1 ) ,
102+ } ;
103+ } ) ;
104+
105+ var app = builder . Build ( ) ;
106+
107+ // Pipeline
108+ if ( app . Environment . IsDevelopment ( ) )
109+ {
110+ app . MapOpenApi ( ) ;
111+ app . MapScalarApiReference ( ) ;
129112}
113+
114+ // Run migrations only outside Testing
115+ if ( ! app . Environment . IsEnvironment ( "Testing" ) )
116+ {
117+ using var scope = app . Services . CreateScope ( ) ;
118+ var db = scope . ServiceProvider . GetRequiredService < AppDbContext > ( ) ;
119+ db . Database . Migrate ( ) ;
120+ }
121+
122+ app . UseHttpsRedirection ( ) ;
123+
124+ app . UseAuthentication ( ) ;
125+ app . UseAuthorization ( ) ;
126+
127+ app . UseCors ( "FrontendDev" ) ;
128+
129+ app . MapControllers ( ) ;
130+
131+ // Simple health endpoint for tests and monitoring
132+ app . MapGet ( "/healthz" , ( ) => Results . Ok ( new { status = "ok" } ) ) ;
133+
134+ app . Run ( ) ;
135+
136+ public partial class Program ;
0 commit comments