dramaling-vocab-learning/backend/DramaLing.Api/Program.cs

169 lines
5.2 KiB
C#

using Microsoft.EntityFrameworkCore;
using DramaLing.Api.Data;
using DramaLing.Api.Services;
using DramaLing.Api.Middleware;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.WriteIndented = true;
});
// Entity Framework - 使用 SQLite 進行測試
var useInMemoryDb = Environment.GetEnvironmentVariable("USE_INMEMORY_DB") == "true";
if (useInMemoryDb)
{
builder.Services.AddDbContext<DramaLingDbContext>(options =>
options.UseSqlite("Data Source=:memory:"));
}
else
{
var connectionString = Environment.GetEnvironmentVariable("DRAMALING_DB_CONNECTION")
?? builder.Configuration.GetConnectionString("DefaultConnection")
?? "Data Source=dramaling_test.db"; // SQLite 檔案
builder.Services.AddDbContext<DramaLingDbContext>(options =>
options.UseSqlite(connectionString));
}
// Custom Services
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddHttpClient<IGeminiService, GeminiService>();
// Authentication - 從環境變數讀取 JWT 配置
var supabaseUrl = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_URL")
?? builder.Configuration["Supabase:Url"]
?? "https://localhost";
var jwtSecret = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_JWT_SECRET")
?? builder.Configuration["Supabase:JwtSecret"]
?? "dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = supabaseUrl,
ValidAudience = "authenticated",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret))
};
});
// CORS for frontend
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins("http://localhost:3000", "http://localhost:3001")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(5));
});
// 開發環境允許所有來源
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "DramaLing API", Version = "v1" });
// JWT Authentication for Swagger
c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
// 全域錯誤處理中介軟體 (必須放在最前面)
app.UseMiddleware<ErrorHandlingMiddleware>();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "DramaLing API v1");
c.RoutePrefix = "swagger";
});
}
// 開發環境使用寬鬆的 CORS 政策
if (app.Environment.IsDevelopment())
{
app.UseCors("AllowAll");
}
else
{
app.UseCors("AllowFrontend");
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// Health check endpoint
app.MapGet("/health", () => new { Status = "Healthy", Timestamp = DateTime.UtcNow });
// 確保資料庫已創建
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<DramaLingDbContext>();
try
{
context.Database.EnsureCreated();
app.Logger.LogInformation("Database ensured created");
}
catch (Exception ex)
{
app.Logger.LogError(ex, "Error creating database");
}
}
app.Run();