a0a0575a99
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 10s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m17s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 12s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
199 lines
6.6 KiB
C#
199 lines
6.6 KiB
C#
using System.Text;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Microsoft.OpenApi;
|
|
using Quartz;
|
|
using Serilog;
|
|
using UniVerse.Api.BackgroundServices;
|
|
using UniVerse.Api.Filters;
|
|
using UniVerse.Api.Middleware;
|
|
using UniVerse.Application.Interfaces;
|
|
using UniVerse.Infrastructure.Services;
|
|
using UniVerse.Infrastructure.Data;
|
|
using UniVerse.Infrastructure.ExternalServices;
|
|
using UniVerse.Infrastructure.Notifications;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
var useAspire = builder.Configuration.GetValue<bool>("Aspire:Enabled");
|
|
|
|
if (useAspire)
|
|
{
|
|
builder.AddServiceDefaults();
|
|
}
|
|
|
|
// --- Serilog ---
|
|
Log.Logger = new LoggerConfiguration()
|
|
.ReadFrom.Configuration(builder.Configuration)
|
|
.Enrich.FromLogContext()
|
|
.WriteTo.Console()
|
|
.CreateLogger();
|
|
builder.Host.UseSerilog();
|
|
|
|
// --- DbContext ---
|
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
{
|
|
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"),
|
|
npgsql =>
|
|
{
|
|
npgsql.EnableRetryOnFailure(3);
|
|
npgsql.MigrationsAssembly("UniVerse.Infrastructure"); // Указывает EF Core, в какой сборке искать/хранить миграции.
|
|
});
|
|
});
|
|
|
|
// --- Authentication ---
|
|
builder.Services.AddAuthentication(options =>
|
|
{
|
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
})
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
|
ValidAudience = builder.Configuration["Jwt:Audience"],
|
|
IssuerSigningKey = new SymmetricSecurityKey(
|
|
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Secret"]!))
|
|
};
|
|
});
|
|
builder.Services.AddAuthorization();
|
|
|
|
// --- CORS ---
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
policy.WithOrigins(
|
|
builder.Configuration.GetSection("Cors:Origins").Get<string[]>()
|
|
?? ["http://localhost:5173", "http://localhost:3000"])
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod()
|
|
.AllowCredentials();
|
|
});
|
|
});
|
|
|
|
// --- Services DI ---
|
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
builder.Services.AddScoped<IUserService, UserService>();
|
|
builder.Services.AddScoped<ITagService, TagService>();
|
|
builder.Services.AddScoped<ILocationService, LocationService>();
|
|
builder.Services.AddScoped<ICourseService, CourseService>();
|
|
builder.Services.AddScoped<ILectureService, LectureService>();
|
|
builder.Services.AddScoped<IReviewService, ReviewService>();
|
|
builder.Services.AddScoped<IGamificationService, GamificationService>();
|
|
builder.Services.AddScoped<IAchievementService, AchievementService>();
|
|
builder.Services.AddScoped<ILlmAnalysisService, LlmAnalysisService>();
|
|
builder.Services.AddScoped<IScheduleSyncService, ScheduleSyncService>();
|
|
builder.Services.AddScoped<INotificationService, NotificationService>();
|
|
builder.Services.AddScoped<INotificationProvider, EmailNotificationProvider>();
|
|
builder.Services.AddSingleton<INotificationScheduler, QuartzNotificationScheduler>();
|
|
builder.Services.AddTransient<NotificationJob>();
|
|
builder.Services.Configure<EmailNotificationOptions>(builder.Configuration.GetSection("Email:Smtp"));
|
|
|
|
builder.Services.AddQuartz();
|
|
builder.Services.AddQuartzHostedService(options =>
|
|
{
|
|
options.WaitForJobsToComplete = true;
|
|
});
|
|
|
|
// --- HTTP Clients ---
|
|
builder.Services.AddHttpClient<ILlmClient, LlmClient>(client =>
|
|
{
|
|
client.BaseAddress = new Uri(builder.Configuration["Llm:BaseUrl"] ?? "https://api.openai.com/v1/");
|
|
client.Timeout = TimeSpan.FromSeconds(60);
|
|
});
|
|
|
|
builder.Services.AddHttpClient<IModeusApiClient, ModeusApiClient>(client =>
|
|
{
|
|
client.BaseAddress = new Uri(builder.Configuration["ModeusApi:BaseUrl"] ?? "https://schedule.rdcenter.ru");
|
|
client.Timeout = TimeSpan.FromSeconds(30);
|
|
});
|
|
|
|
// --- Background Services ---
|
|
builder.Services.AddHostedService<LlmProcessingBackgroundService>();
|
|
|
|
// --- Controllers ---
|
|
builder.Services.AddControllers()
|
|
.AddJsonOptions(o =>
|
|
{
|
|
o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
|
o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
|
});
|
|
|
|
// --- Swagger ---
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen(options =>
|
|
{
|
|
options.SwaggerDoc("v1", new OpenApiInfo
|
|
{
|
|
Title = "UniVerse API",
|
|
Version = "v1",
|
|
Description =
|
|
"REST API веб-платформы UniVerse.\n\n" +
|
|
"Аутентификация: JWT Bearer (получить через `POST /api/v1/auth/login/microsoft` или `POST /api/v1/auth/login/dev` в Development).",
|
|
Contact = new OpenApiContact
|
|
{
|
|
Name = "UniVerse Dev"
|
|
}
|
|
});
|
|
|
|
// Bearer security scheme definition (used per-endpoint by AuthorizeOperationFilter)
|
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
{
|
|
Name = "Authorization",
|
|
Type = SecuritySchemeType.Http,
|
|
Scheme = "bearer",
|
|
BearerFormat = "JWT",
|
|
In = ParameterLocation.Header,
|
|
Description = "Введите JWT access token, полученный из `/api/v1/auth/login/microsoft`.\n\nПример: `eyJhbGci...`"
|
|
});
|
|
|
|
// Include XML doc comments generated from controller /// summaries
|
|
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
|
if (File.Exists(xmlPath))
|
|
options.IncludeXmlComments(xmlPath);
|
|
|
|
// Per-endpoint security requirement + role documentation (replaces global AddSecurityRequirement)
|
|
options.OperationFilter<AuthorizeOperationFilter>();
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
if (useAspire)
|
|
{
|
|
app.MapDefaultEndpoints();
|
|
}
|
|
|
|
// --- Middleware Pipeline ---
|
|
app.UseMiddleware<RequestLoggingMiddleware>();
|
|
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger(c =>
|
|
{
|
|
c.RouteTemplate = "api/docs/{documentName}/swagger.json";
|
|
});
|
|
|
|
app.UseSwaggerUI(c =>
|
|
{
|
|
c.RoutePrefix = "api/docs";
|
|
c.SwaggerEndpoint("v1/swagger.json", "UniVerse API v1");
|
|
});
|
|
}
|
|
|
|
app.UseCors();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.MapControllers();
|
|
|
|
app.Run();
|