Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PowderCoating.Core\PowderCoating.Core.csproj" />
<ProjectReference Include="..\PowderCoating.Application\PowderCoating.Application.csproj" />
<ProjectReference Include="..\PowderCoating.Infrastructure\PowderCoating.Infrastructure.csproj" />
<ProjectReference Include="..\PowderCoating.Shared\PowderCoating.Shared.csproj" />
</ItemGroup>
</Project>
+210
View File
@@ -0,0 +1,210 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Infrastructure.Repositories;
using Serilog;
using AutoMapper;
using PowderCoating.Application.Mappings;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/api-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.Host.UseSerilog();
// Add services to the container
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
// Configure Identity
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 12;
options.Password.RequiredUniqueChars = 4;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configure JWT Authentication
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["SecretKey"] ?? throw new InvalidOperationException("JWT Secret Key not found.");
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 = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ClockSkew = TimeSpan.Zero
};
});
// Register repositories and services
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
// Configure AutoMapper
builder.Services.AddSingleton<IMapper>(sp =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new CustomerProfile());
cfg.AddProfile(new JobProfile());
}, loggerFactory);
return config.CreateMapper();
});
// Add CORS - Configured per environment
var allowedOrigins = builder.Configuration.GetSection("CorsSettings:AllowedOrigins").Get<string[]>()
?? new[] { "http://localhost:3000" }; // Default for development
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowMobileApp",
policy =>
{
policy.WithOrigins(allowedOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
// Add controllers
builder.Services.AddControllers();
// Configure Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Powder Coating Management API",
Version = "v1",
Description = "API for Powder Coating Management System",
Contact = new OpenApiContact
{
Name = "Your Company",
Email = "support@yourcompany.com"
}
});
// Add JWT Authentication to Swagger
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
var app = builder.Build();
// Global exception handler — returns RFC 7807 Problem Details for all unhandled exceptions
app.UseExceptionHandler(exApp => exApp.Run(async ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status500InternalServerError;
ctx.Response.ContentType = "application/problem+json";
var feature = ctx.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>();
if (feature?.Error != null)
{
var logger = ctx.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(feature.Error, "Unhandled exception in API request {Method} {Path}",
ctx.Request.Method, ctx.Request.Path);
}
await ctx.Response.WriteAsJsonAsync(new
{
type = "https://tools.ietf.org/html/rfc7807",
title = "An unexpected error occurred.",
status = 500,
traceId = System.Diagnostics.Activity.Current?.Id ?? ctx.TraceIdentifier
});
}));
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Powder Coating API v1");
c.RoutePrefix = string.Empty; // Swagger at root
});
}
app.UseHttpsRedirection();
app.UseCors("AllowMobileApp");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// Run migrations and seed data
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
await context.Database.MigrateAsync();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}
app.Run();
@@ -0,0 +1,12 @@
{
"profiles": {
"PowderCoating.Api": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:58459;http://localhost:58460"
}
}
}
+34
View File
@@ -0,0 +1,34 @@
{
"ConnectionStrings": {
"DefaultConnection": "USE_USER_SECRETS_OR_ENVIRONMENT_VARIABLE"
},
"JwtSettings": {
"SecretKey": "USE_USER_SECRETS_OR_ENVIRONMENT_VARIABLE",
"Issuer": "PowderCoatingAPI",
"Audience": "PowderCoatingMobileApp",
"ExpirationMinutes": 15,
"RefreshTokenExpirationDays": 7
},
"CorsSettings": {
"AllowedOrigins": [
"http://localhost:3000",
"http://localhost:5173"
]
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
},
"AllowedHosts": "localhost;127.0.0.1"
}