@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,332 lines (1,198 loc) • 55.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aspnetSwaggerTemplate = void 0;
exports.aspnetSwaggerTemplate = {
id: 'aspnet-swagger',
name: 'aspnet-swagger',
displayName: 'ASP.NET Core with Swagger/OpenAPI',
description: 'Enterprise .NET API with comprehensive Swagger/OpenAPI documentation and XML comments',
language: 'csharp',
framework: 'aspnet-swagger',
version: '1.0.0',
tags: ['aspnet', 'swagger', 'openapi', 'documentation', 'xml-docs'],
port: 5000,
dependencies: {},
features: ['authentication', 'database', 'validation', 'logging', 'testing'],
files: {
// Project file with Swagger and XML documentation packages
'{{serviceName}}.csproj': `<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Enable XML documentation generation -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn> <!-- Disable missing XML comment warnings -->
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<!-- Comprehensive Swagger/OpenAPI Packages -->
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.12" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.1.0" />
<PackageReference Include="Microsoft.OpenApi" Version="1.6.14" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<!-- Additional documentation and analysis tools -->
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="13.20.0" />
<PackageReference Include="NSwag.MSBuild" Version="13.20.0" />
</ItemGroup>
<!-- XML Documentation files to include -->
<ItemGroup>
<Content Include="docs/**/*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<!-- NSwag configuration for code generation -->
<Target Name="NSwag" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Debug'">
<Exec Command="$(NSwagExe_Net80) run nswag.json" />
</Target>
</Project>`,
// Program.cs with comprehensive Swagger configuration
'Program.cs': `using {{serviceName}}.Data;
using {{serviceName}}.Services;
using {{serviceName}}.Models;
using {{serviceName}}.DTOs;
using {{serviceName}}.Profiles;
using {{serviceName}}.Validators;
using {{serviceName}}.Infrastructure.Swagger;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using FluentValidation;
using Serilog;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
using System.Text;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.Host.UseSerilog();
// Add services to the container
builder.Services.AddControllers(options =>
{
// Add custom model binding and validation
options.SuppressAsyncSuffixInActionNames = false;
})
.AddNewtonsoftJson(options =>
{
// Configure JSON serialization for Swagger
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
// API versioning
builder.Services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("X-Version"),
new MediaTypeApiVersionReader("ver")
);
});
builder.Services.AddVersionedApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
});
builder.Services.AddEndpointsApiExplorer();
// Configure Entity Framework
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
if (builder.Environment.IsEnvironment("Testing"))
{
options.UseInMemoryDatabase("TestDb");
}
else
{
options.UseSqlServer(connectionString);
}
});
// AutoMapper
builder.Services.AddAutoMapper(typeof(UserProfile));
// FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<CreateUserValidator>();
// Custom services
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IAuditService, AuditService>();
// Authentication
var jwtKey = builder.Configuration["Jwt:Key"] ?? "YourSecretKeyHere";
var key = Encoding.ASCII.GetBytes(jwtKey);
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
// Configure comprehensive Swagger/OpenAPI
builder.Services.AddSwaggerGen(c =>
{
// Basic API information
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "{{serviceName}} API",
Description = "A comprehensive .NET API with full OpenAPI documentation",
TermsOfService = new Uri("https://{{serviceName}}.com/terms"),
Contact = new OpenApiContact
{
Name = "{{serviceName}} Support",
Email = "support@{{serviceName}}.com",
Url = new Uri("https://{{serviceName}}.com/contact")
},
License = new OpenApiLicense
{
Name = "MIT License",
Url = new Uri("https://opensource.org/licenses/MIT")
}
});
c.SwaggerDoc("v2", new OpenApiInfo
{
Version = "v2",
Title = "{{serviceName}} API v2",
Description = "Version 2 of the {{serviceName}} API with enhanced features",
TermsOfService = new Uri("https://{{serviceName}}.com/terms"),
Contact = new OpenApiContact
{
Name = "{{serviceName}} Support",
Email = "support@{{serviceName}}.com",
Url = new Uri("https://{{serviceName}}.com/contact")
},
License = new OpenApiLicense
{
Name = "MIT License",
Url = new Uri("https://opensource.org/licenses/MIT")
}
});
// Enable annotations
c.EnableAnnotations();
// Include XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
// Include XML comments from referenced assemblies
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
var assemblyXmlPath = Path.Combine(AppContext.BaseDirectory, $"{assembly.GetName().Name}.xml");
if (File.Exists(assemblyXmlPath))
{
c.IncludeXmlComments(assemblyXmlPath);
}
}
// Add JWT Bearer authentication
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "Enter 'Bearer' followed by a space and your JWT token. Example: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
// Add API Key authentication
c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Name = "X-API-Key",
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Description = "API Key authentication"
});
// Custom schema mappings
c.MapType<DateTime>(() => new OpenApiSchema
{
Type = "string",
Format = "date-time",
Example = new Microsoft.OpenApi.Any.OpenApiString(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))
});
c.MapType<DateOnly>(() => new OpenApiSchema
{
Type = "string",
Format = "date",
Example = new Microsoft.OpenApi.Any.OpenApiString(DateOnly.FromDateTime(DateTime.UtcNow).ToString("yyyy-MM-dd"))
});
c.MapType<TimeOnly>(() => new OpenApiSchema
{
Type = "string",
Format = "time",
Example = new Microsoft.OpenApi.Any.OpenApiString(TimeOnly.FromDateTime(DateTime.UtcNow).ToString("HH:mm:ss"))
});
// Custom operation filters
c.OperationFilter<SwaggerDefaultValues>();
c.OperationFilter<AuthorizeCheckOperationFilter>();
c.OperationFilter<AddResponseHeadersFilter>();
// Custom schema filters
c.SchemaFilter<RequiredNotNullableSchemaFilter>();
c.SchemaFilter<EnumSchemaFilter>();
// Document filters
c.DocumentFilter<TagDescriptionsDocumentFilter>();
// Group actions by namespace
c.TagActionsBy(api => new[] { api.GroupName ?? api.ActionDescriptor.RouteValues["controller"] });
c.DocInclusionPredicate((name, api) => true);
// Custom ordering
c.OrderActionsBy(apiDesc => $"{apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.HttpMethod}");
// Add examples
c.ExampleFilters();
// Configure for different environments
if (builder.Environment.IsDevelopment())
{
c.DescribeAllParametersInCamelCase();
}
});
// Add example filters
builder.Services.AddSwaggerExamplesFromAssemblyOf<UserCreateExample>();
// Add Swagger annotations
builder.Services.AddSwaggerAnnotations();
// Health checks
builder.Services.AddHealthChecks()
.AddDbContext<ApplicationDbContext>();
var app = builder.Build();
var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment() || app.Environment.IsStaging())
{
app.UseSwagger(c =>
{
c.RouteTemplate = "swagger/{documentName}/swagger.json";
c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
swaggerDoc.Servers = new List<OpenApiServer>
{
new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" }
};
});
});
app.UseSwaggerUI(c =>
{
// Add endpoints for each API version
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
c.SwaggerEndpoint(
$"/swagger/{description.GroupName}/swagger.json",
$"{{serviceName}} API {description.GroupName.ToUpperInvariant()}"
);
}
// Customize Swagger UI
c.RoutePrefix = string.Empty; // Serve at root
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.List);
c.DefaultModelExpandDepth(2);
c.DefaultModelRendering(Swashbuckle.AspNetCore.SwaggerUI.ModelRendering.Example);
c.DisplayRequestDuration();
c.EnableDeepLinking();
c.EnableFilter();
c.EnableValidator();
c.ShowExtensions();
c.ShowCommonExtensions();
c.EnablePersistAuthorization();
// Custom CSS and JavaScript
c.InjectStylesheet("/swagger-ui/custom.css");
c.InjectJavascript("/swagger-ui/custom.js");
// OAuth configuration (if needed)
c.OAuthClientId("swagger-ui");
c.OAuthAppName("{{serviceName}} API");
c.OAuthUsePkce();
// Additional configuration
c.ConfigObject.AdditionalItems.Add("requestSnippetsEnabled", true);
c.ConfigObject.AdditionalItems.Add("syntaxHighlight", new Dictionary<string, object>
{
["activated"] = true,
["theme"] = "tomorrow-night"
});
});
// Serve ReDoc as alternative documentation
app.UseReDoc(c =>
{
c.RoutePrefix = "redoc";
c.SpecUrl("/swagger/v1/swagger.json");
c.DocumentTitle = "{{serviceName}} API Documentation";
c.EnableUntrustedSpec();
c.ScrollYOffset(10);
c.HideHostname();
c.HideDownloadButton();
c.ExpandResponses("200,201");
c.RequiredPropsFirst();
c.NoAutoAuth();
c.PathInMiddlePanel();
c.HideLoading();
c.NativeScrollbars();
c.DisableSearch();
c.OnlyRequiredInSamples();
c.SortPropsAlphabetically();
});
}
// Request logging middleware
app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
// Serve static files for Swagger customization
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// Health check endpoints
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready");
// API documentation endpoints
app.MapGet("/", () => Results.Redirect("/swagger"));
app.MapGet("/docs", () => Results.Redirect("/swagger"));
app.MapGet("/api-docs", () => Results.Redirect("/swagger"));
app.Run();`,
// Enhanced user controller with comprehensive documentation
'Controllers/UsersController.cs': `using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using {{serviceName}}.Services;
using {{serviceName}}.DTOs;
using {{serviceName}}.Models;
using AutoMapper;
using FluentValidation;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
using System.Net;
namespace {{serviceName}}.Controllers;
/// <summary>
/// Manages user operations including registration, authentication, and profile management
/// </summary>
/// <remarks>
/// This controller provides comprehensive user management functionality with full CRUD operations.
/// All endpoints are documented with OpenAPI specifications and include proper error handling.
/// </remarks>
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[Produces("application/json")]
[SwaggerTag("User Management", "Operations related to user accounts and profiles")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IMapper _mapper;
private readonly IValidator<CreateUserRequest> _createValidator;
private readonly IValidator<UpdateUserRequest> _updateValidator;
private readonly ILogger<UsersController> _logger;
/// <summary>
/// Initializes a new instance of the UsersController
/// </summary>
/// <param name="userService">User service for business logic</param>
/// <param name="mapper">AutoMapper instance for object mapping</param>
/// <param name="createValidator">Validator for user creation requests</param>
/// <param name="updateValidator">Validator for user update requests</param>
/// <param name="logger">Logger instance for logging operations</param>
public UsersController(
IUserService userService,
IMapper mapper,
IValidator<CreateUserRequest> createValidator,
IValidator<UpdateUserRequest> updateValidator,
ILogger<UsersController> logger)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_createValidator = createValidator ?? throw new ArgumentNullException(nameof(createValidator));
_updateValidator = updateValidator ?? throw new ArgumentNullException(nameof(updateValidator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Retrieves all users with optional filtering and pagination
/// </summary>
/// <param name="search">Optional search term to filter users by name or email</param>
/// <param name="page">Page number for pagination (default: 1)</param>
/// <param name="pageSize">Number of items per page (default: 10, max: 100)</param>
/// <param name="sortBy">Field to sort by (name, email, createdAt)</param>
/// <param name="sortOrder">Sort order (asc, desc)</param>
/// <returns>A paginated list of users</returns>
/// <response code="200">Returns the list of users</response>
/// <response code="400">Invalid pagination or sort parameters</response>
/// <response code="401">Unauthorized access</response>
/// <response code="500">Internal server error</response>
[HttpGet]
[Authorize]
[SwaggerOperation(
Summary = "Get all users",
Description = "Retrieves a paginated list of users with optional search and sorting capabilities",
OperationId = "GetUsers",
Tags = new[] { "Users" }
)]
[SwaggerResponse(200, "Users retrieved successfully", typeof(PagedResult<UserResponse>))]
[SwaggerResponse(400, "Invalid request parameters", typeof(ErrorResponse))]
[SwaggerResponse(401, "Unauthorized", typeof(ErrorResponse))]
[SwaggerResponse(500, "Internal server error", typeof(ErrorResponse))]
[SwaggerResponseExample(200, typeof(UserListExample))]
public async Task<ActionResult<PagedResult<UserResponse>>> GetUsers(
[FromQuery, SwaggerParameter("Search term for filtering users")] string? search = null,
[FromQuery, SwaggerParameter("Page number (1-based)")] int page = 1,
[FromQuery, SwaggerParameter("Items per page (1-100)")] int pageSize = 10,
[FromQuery, SwaggerParameter("Sort field (name, email, createdAt)")] string sortBy = "createdAt",
[FromQuery, SwaggerParameter("Sort order (asc, desc)")] string sortOrder = "desc")
{
try
{
_logger.LogInformation("Getting users with search: {Search}, page: {Page}, pageSize: {PageSize}",
search, page, pageSize);
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 10;
var users = await _userService.GetUsersAsync(search, page, pageSize, sortBy, sortOrder);
var response = _mapper.Map<PagedResult<UserResponse>>(users);
return Ok(response);
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "Invalid parameters for GetUsers");
return BadRequest(new ErrorResponse { Message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving users");
return StatusCode(500, new ErrorResponse { Message = "An error occurred while retrieving users" });
}
}
/// <summary>
/// Retrieves a specific user by their unique identifier
/// </summary>
/// <param name="id">The unique identifier of the user</param>
/// <returns>The user details</returns>
/// <response code="200">User found and returned</response>
/// <response code="404">User not found</response>
/// <response code="401">Unauthorized access</response>
/// <response code="500">Internal server error</response>
[HttpGet("{id:int}")]
[Authorize]
[SwaggerOperation(
Summary = "Get user by ID",
Description = "Retrieves detailed information about a specific user",
OperationId = "GetUserById",
Tags = new[] { "Users" }
)]
[SwaggerResponse(200, "User found", typeof(UserResponse))]
[SwaggerResponse(404, "User not found", typeof(ErrorResponse))]
[SwaggerResponse(401, "Unauthorized", typeof(ErrorResponse))]
[SwaggerResponse(500, "Internal server error", typeof(ErrorResponse))]
[SwaggerResponseExample(200, typeof(UserResponseExample))]
[SwaggerResponseExample(404, typeof(NotFoundErrorExample))]
public async Task<ActionResult<UserResponse>> GetUser(
[FromRoute, SwaggerParameter("User ID", Required = true)] int id)
{
try
{
_logger.LogInformation("Getting user with ID: {UserId}", id);
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
{
_logger.LogWarning("User with ID {UserId} not found", id);
return NotFound(new ErrorResponse { Message = $"User with ID {id} not found" });
}
var response = _mapper.Map<UserResponse>(user);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving user {UserId}", id);
return StatusCode(500, new ErrorResponse { Message = "An error occurred while retrieving the user" });
}
}
/// <summary>
/// Creates a new user account
/// </summary>
/// <param name="request">User creation details</param>
/// <returns>The created user</returns>
/// <response code="201">User created successfully</response>
/// <response code="400">Invalid user data or validation errors</response>
/// <response code="409">User with email already exists</response>
/// <response code="500">Internal server error</response>
[HttpPost]
[SwaggerOperation(
Summary = "Create new user",
Description = "Creates a new user account with the provided information",
OperationId = "CreateUser",
Tags = new[] { "Users" }
)]
[SwaggerResponse(201, "User created successfully", typeof(UserResponse))]
[SwaggerResponse(400, "Invalid request data", typeof(ValidationErrorResponse))]
[SwaggerResponse(409, "Email already exists", typeof(ErrorResponse))]
[SwaggerResponse(500, "Internal server error", typeof(ErrorResponse))]
[SwaggerRequestExample(typeof(CreateUserRequest), typeof(UserCreateExample))]
[SwaggerResponseExample(201, typeof(UserCreatedExample))]
[SwaggerResponseExample(400, typeof(ValidationErrorExample))]
public async Task<ActionResult<UserResponse>> CreateUser(
[FromBody, SwaggerRequestBody("User creation data", Required = true)] CreateUserRequest request)
{
try
{
_logger.LogInformation("Creating new user with email: {Email}", request.Email);
var validationResult = await _createValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.ToDictionary(e => e.PropertyName, e => e.ErrorMessage);
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var user = _mapper.Map<User>(request);
var createdUser = await _userService.CreateUserAsync(user);
var response = _mapper.Map<UserResponse>(createdUser);
_logger.LogInformation("User created successfully with ID: {UserId}", createdUser.Id);
return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, response);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("email"))
{
_logger.LogWarning(ex, "Attempt to create user with existing email: {Email}", request.Email);
return Conflict(new ErrorResponse { Message = "A user with this email already exists" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user with email: {Email}", request.Email);
return StatusCode(500, new ErrorResponse { Message = "An error occurred while creating the user" });
}
}
/// <summary>
/// Updates an existing user's information
/// </summary>
/// <param name="id">The unique identifier of the user to update</param>
/// <param name="request">Updated user information</param>
/// <returns>The updated user</returns>
/// <response code="200">User updated successfully</response>
/// <response code="400">Invalid request data or validation errors</response>
/// <response code="404">User not found</response>
/// <response code="401">Unauthorized access</response>
/// <response code="500">Internal server error</response>
[HttpPut("{id:int}")]
[Authorize]
[SwaggerOperation(
Summary = "Update user",
Description = "Updates an existing user's information",
OperationId = "UpdateUser",
Tags = new[] { "Users" }
)]
[SwaggerResponse(200, "User updated successfully", typeof(UserResponse))]
[SwaggerResponse(400, "Invalid request data", typeof(ValidationErrorResponse))]
[SwaggerResponse(404, "User not found", typeof(ErrorResponse))]
[SwaggerResponse(401, "Unauthorized", typeof(ErrorResponse))]
[SwaggerResponse(500, "Internal server error", typeof(ErrorResponse))]
[SwaggerRequestExample(typeof(UpdateUserRequest), typeof(UserUpdateExample))]
[SwaggerResponseExample(200, typeof(UserUpdatedExample))]
public async Task<ActionResult<UserResponse>> UpdateUser(
[FromRoute, SwaggerParameter("User ID", Required = true)] int id,
[FromBody, SwaggerRequestBody("Updated user data", Required = true)] UpdateUserRequest request)
{
try
{
_logger.LogInformation("Updating user with ID: {UserId}", id);
var validationResult = await _updateValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.ToDictionary(e => e.PropertyName, e => e.ErrorMessage);
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var existingUser = await _userService.GetUserByIdAsync(id);
if (existingUser == null)
{
_logger.LogWarning("User with ID {UserId} not found for update", id);
return NotFound(new ErrorResponse { Message = $"User with ID {id} not found" });
}
_mapper.Map(request, existingUser);
var updatedUser = await _userService.UpdateUserAsync(existingUser);
var response = _mapper.Map<UserResponse>(updatedUser);
_logger.LogInformation("User {UserId} updated successfully", id);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user {UserId}", id);
return StatusCode(500, new ErrorResponse { Message = "An error occurred while updating the user" });
}
}
/// <summary>
/// Deletes a user account (soft delete)
/// </summary>
/// <param name="id">The unique identifier of the user to delete</param>
/// <returns>No content if successful</returns>
/// <response code="204">User deleted successfully</response>
/// <response code="404">User not found</response>
/// <response code="401">Unauthorized access</response>
/// <response code="500">Internal server error</response>
[HttpDelete("{id:int}")]
[Authorize]
[SwaggerOperation(
Summary = "Delete user",
Description = "Soft deletes a user account (user data is retained but marked as deleted)",
OperationId = "DeleteUser",
Tags = new[] { "Users" }
)]
[SwaggerResponse(204, "User deleted successfully")]
[SwaggerResponse(404, "User not found", typeof(ErrorResponse))]
[SwaggerResponse(401, "Unauthorized", typeof(ErrorResponse))]
[SwaggerResponse(500, "Internal server error", typeof(ErrorResponse))]
public async Task<IActionResult> DeleteUser(
[FromRoute, SwaggerParameter("User ID", Required = true)] int id)
{
try
{
_logger.LogInformation("Deleting user with ID: {UserId}", id);
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
{
_logger.LogWarning("User with ID {UserId} not found for deletion", id);
return NotFound(new ErrorResponse { Message = $"User with ID {id} not found" });
}
await _userService.DeleteUserAsync(id);
_logger.LogInformation("User {UserId} deleted successfully", id);
return NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting user {UserId}", id);
return StatusCode(500, new ErrorResponse { Message = "An error occurred while deleting the user" });
}
}
/// <summary>
/// Searches users by various criteria (v2 endpoint with enhanced features)
/// </summary>
/// <param name="query">Search query</param>
/// <param name="filters">Advanced search filters</param>
/// <returns>Search results</returns>
/// <response code="200">Search completed successfully</response>
/// <response code="400">Invalid search parameters</response>
/// <response code="401">Unauthorized access</response>
[HttpPost("search")]
[MapToApiVersion("2.0")]
[Authorize]
[SwaggerOperation(
Summary = "Advanced user search (v2)",
Description = "Performs advanced search across user data with multiple criteria",
OperationId = "SearchUsersV2",
Tags = new[] { "Users" }
)]
[SwaggerResponse(200, "Search results", typeof(SearchResult<UserResponse>))]
[SwaggerResponse(400, "Invalid search parameters", typeof(ErrorResponse))]
[SwaggerResponse(401, "Unauthorized", typeof(ErrorResponse))]
public async Task<ActionResult<SearchResult<UserResponse>>> SearchUsers(
[FromBody] SearchRequest query)
{
try
{
_logger.LogInformation("Performing advanced user search");
var results = await _userService.SearchUsersAsync(query);
var response = _mapper.Map<SearchResult<UserResponse>>(results);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error performing user search");
return StatusCode(500, new ErrorResponse { Message = "An error occurred during search" });
}
}
}`,
// Swagger configuration and filters
'Infrastructure/Swagger/SwaggerDefaultValues.cs': `using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace {{serviceName}}.Infrastructure.Swagger;
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
{
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
var response = operation.Responses[responseKey];
foreach (var contentType in response.Content.Keys)
{
if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType))
{
response.Content.Remove(contentType);
}
}
}
if (operation.Parameters == null)
return;
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
parameter.Description ??= description.ModelMetadata?.Description;
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new Microsoft.OpenApi.Any.OpenApiString(description.DefaultValue.ToString());
}
parameter.Required |= description.IsRequired;
}
}
}`,
'Infrastructure/Swagger/AuthorizeCheckOperationFilter.cs': `using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace {{serviceName}}.Infrastructure.Swagger;
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize = context.MethodInfo.DeclaringType?.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>()
.Any() ?? false;
if (hasAuthorize)
{
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}
] = new[] { "Bearer" }
}
};
}
}
}`,
'Infrastructure/Swagger/RequiredNotNullableSchemaFilter.cs': `using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace {{serviceName}}.Infrastructure.Swagger;
public class RequiredNotNullableSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema.Properties == null)
return;
var notNullableProperties = schema
.Properties
.Where(x => !x.Value.Nullable && !schema.Required.Contains(x.Key))
.ToList();
foreach (var property in notNullableProperties)
{
schema.Required.Add(property.Key);
}
}
}`,
'Infrastructure/Swagger/EnumSchemaFilter.cs': `using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.ComponentModel;
namespace {{serviceName}}.Infrastructure.Swagger;
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
schema.Enum.Clear();
foreach (var enumValue in Enum.GetValues(context.Type))
{
var member = context.Type.GetMember(enumValue.ToString()!)[0];
var description = member.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault()?.Description;
schema.Enum.Add(new Microsoft.OpenApi.Any.OpenApiString(enumValue.ToString()));
if (description != null)
{
if (schema.Extensions.ContainsKey("x-enum-descriptions"))
{
var descriptions = (Dictionary<string, object>)schema.Extensions["x-enum-descriptions"];
descriptions[enumValue.ToString()!] = description;
}
else
{
schema.Extensions.Add("x-enum-descriptions", new Dictionary<string, object>
{
[enumValue.ToString()!] = description
});
}
}
}
}
}
}`,
'Infrastructure/Swagger/TagDescriptionsDocumentFilter.cs': `using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace {{serviceName}}.Infrastructure.Swagger;
public class TagDescriptionsDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Tags = new List<OpenApiTag>
{
new OpenApiTag
{
Name = "Users",
Description = "Operations related to user management, authentication, and profiles"
},
new OpenApiTag
{
Name = "Products",
Description = "Product catalog management and inventory operations"
},
new OpenApiTag
{
Name = "Orders",
Description = "Order processing, tracking, and management"
},
new OpenApiTag
{
Name = "Authentication",
Description = "User authentication, token management, and security operations"
}
};
}
}`,
'Infrastructure/Swagger/AddResponseHeadersFilter.cs': `using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace {{serviceName}}.Infrastructure.Swagger;
public class AddResponseHeadersFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
foreach (var response in operation.Responses)
{
response.Value.Headers ??= new Dictionary<string, OpenApiHeader>();
response.Value.Headers.Add("X-Correlation-ID", new OpenApiHeader
{
Description = "Unique request correlation identifier",
Schema = new OpenApiSchema { Type = "string" }
});
response.Value.Headers.Add("X-Request-ID", new OpenApiHeader
{
Description = "Unique request identifier",
Schema = new OpenApiSchema { Type = "string" }
});
response.Value.Headers.Add("X-RateLimit-Remaining", new OpenApiHeader
{
Description = "Number of requests remaining in current time window",
Schema = new OpenApiSchema { Type = "integer" }
});
}
}
}`,
// Swagger example providers
'Infrastructure/Swagger/Examples/UserCreateExample.cs': `using Swashbuckle.AspNetCore.Filters;
using {{serviceName}}.DTOs;
namespace {{serviceName}}.Infrastructure.Swagger;
public class UserCreateExample : IExamplesProvider<CreateUserRequest>
{
public CreateUserRequest GetExamples()
{
return new CreateUserRequest
{
FirstName = "John",
LastName = "Doe",
Email = "john.doe@example.com",
Password = "SecurePassword123!",
PhoneNumber = "+1-555-123-4567",
DateOfBirth = new DateOnly(1990, 5, 15),
Address = new AddressRequest
{
Street = "123 Main St",
City = "Springfield",
State = "IL",
PostalCode = "62701",
Country = "United States"
}
};
}
}`,
'Infrastructure/Swagger/Examples/UserResponseExample.cs': `using Swashbuckle.AspNetCore.Filters;
using {{serviceName}}.DTOs;
namespace {{serviceName}}.Infrastructure.Swagger;
public class UserResponseExample : IExamplesProvider<UserResponse>
{
public UserResponse GetExamples()
{
return new UserResponse
{
Id = 1,
FirstName = "John",
LastName = "Doe",
Email = "john.doe@example.com",
PhoneNumber = "+1-555-123-4567",
DateOfBirth = new DateOnly(1990, 5, 15),
CreatedAt = DateTime.UtcNow.AddDays(-30),
UpdatedAt = DateTime.UtcNow.AddDays(-5),
IsActive = true,
Address = new AddressResponse
{
Street = "123 Main St",
City = "Springfield",
State = "IL",
PostalCode = "62701",
Country = "United States"
}
};
}
}`,
// Static files for Swagger customization
'wwwroot/swagger-ui/custom.css': `.swagger-ui .topbar {
background-color: #2c3e50;
}
.swagger-ui .topbar .download-url-wrapper {
display: none;
}
.swagger-ui .info .title {
color: #2c3e50;
}
.swagger-ui .scheme-container {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 10px;
margin: 10px 0;
}
.swagger-ui .opblock.opblock-post {
border-color: #28a745;
background: rgba(40, 167, 69, 0.1);
}
.swagger-ui .opblock.opblock-get {
border-color: #17a2b8;
background: rgba(23, 162, 184, 0.1);
}
.swagger-ui .opblock.opblock-put {
border-color: #ffc107;
background: rgba(255, 193, 7, 0.1);
}
.swagger-ui .opblock.opblock-delete {
border-color: #dc3545;
background: rgba(220, 53, 69, 0.1);
}
.swagger-ui .opblock-summary {
font-weight: 600;
}
.swagger-ui .opblock .opblock-section-header h4 {
font-size: 16px;
margin: 0;
}
.swagger-ui .model-box {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
}
.swagger-ui .model .model-title {
color: #495057;
font-weight: 600;
}`,
'wwwroot/swagger-ui/custom.js': `// Custom JavaScript for Swagger UI enhancements
window.addEventListener('DOMContentLoaded', function() {
// Add custom functionality
console.log('{{serviceName}} API Documentation loaded');
// Add copy-to-clipboard functionality for code examples
function addCopyButtons() {
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach(function(codeBlock) {
const button = document.createElement('button');
button.className = 'copy-button';
button.textContent = 'Copy';
button.style.cssText = 'position: absolute; top: 5px; right: 5px; padding: 4px 8px; font-size: 12px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;';
const pre = codeBlock.parentNode;
pre.style.position = 'relative';
pre.appendChild(button);
button.addEventListener('click', function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
button.textContent = 'Copied!';
setTimeout(() => {
button.textContent = 'Copy';
}, 2000);
});
});
});
}
// Add copy buttons after a delay to ensure content is loaded
setTimeout(addCopyButtons, 1000);
// Re-add copy buttons when new content is loaded
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
addCopyButtons();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});`,
// NSwag configuration for code generation
'nswag.json': `{
"runtime": "Net80",
"defaultVariables": null,
"documentGenerator": {
"aspNetCoreToOpenApi": {
"project": "{{serviceName}}.csproj",
"msBuildProjectExtensionsPath": null,
"configuration": null,
"runtime": null,
"targetFramework": null,
"noBuild": false,
"msBuildOutputPath": null,
"verbose": true,
"workingDirectory": null,
"aspNetCoreEnvironment": "Development",
"output": null,
"newLineBehavior": "Auto"
}
},
"codeGenerators": {
"openApiToCSharpClient": {
"clientBaseClass": null,
"configurationClass": null,
"generateClientClasses": true,
"generateClientInterfaces": true,
"clientBaseInterface": null,
"injectHttpClient": true,
"disposeHttpClient": true,
"protectedMethods": [],
"generateExceptionClasses": true,
"exceptionClass": "ApiException",
"wrapDtoExceptions": true,
"useHttpClientCreationMethod": false,
"httpClientType": "System.Net.Http.HttpClient",
"useHttpRequestMessageCreationMethod": false,
"useBaseUrl": true,
"generateBaseUrlProperty": true,
"generateSyncMethods": false,
"generatePrepareRequestAndProcessResponseAsAsyncMethods": false,
"exposeJsonSerializerSettings": false,
"clientClassAccessModifier": "public",
"typeAccessModifier": "public",
"generateContractsOutput": false,
"contractsNamespace": null,
"contractsOutputFilePath": null,
"parameterDateTimeFormat": "s",
"parameterDateFormat": "yyyy-MM-dd",
"generateUpdateJsonSerializerSettingsMethod": true,
"useRequestAndResponseSerializationSettings": false,
"serializeTypeInformation": false,
"queryNullValue": "",
"className": "{controller}Client",
"operationGenerationMode": "MultipleClientsFromOperationId",
"additionalNamespaceUsages": [],
"additionalContractNamespaceUsages": [],
"generateOptionalParameters": false,
"generateJsonMethods": false,
"enforceFlagEnums": false,
"parameterArrayType": "System.Collections.Generic.IEnumerable",
"parameterDictionaryType": "System.Collections.Generic.IDictionary",
"responseArrayType": "System.Collections.Generic.ICollection",
"responseDictionaryType": "System.Collections.Generic.IDictionary",
"wrapResponses": false,
"wrapResponseMethods": [],
"generateResponseClasses": true,
"responseClass": "SwaggerResponse",
"namespace": "{{serviceName}}.ApiClient",
"requiredPropertiesMustBeDefined": true,
"dateType": "System.DateTimeOffset",
"jsonConverters": null,
"anyType": "object",
"dateTimeType": "System.DateTimeOffset",
"timeType": "System.TimeSpan",
"timeSpanType": "System.TimeSpan",
"arrayType": "System.Collections.Generic.ICollection",
"arrayInstanceType": "System.Collections.ObjectModel.Collection",
"dictionaryType": "System.Collections.Generic.IDictionary",
"dictionaryInstanceType": "System.Collections.Generic.Dictionary",
"arrayBaseType": "System.Collections.ObjectModel.Collection",
"dictionaryBaseType": "System.Collections.Generic.Dictionary",
"classStyle": "Poco",
"jsonLibrary": "NewtonsoftJson",
"generateDefaultValues": true,
"generateDataAnnotations": true,
"excludedTypeNames": [],
"excludedParameterNames": [],
"handleReferences": false,
"generateImmutableArrayProperties": false,
"generateImmutableDictionaryProperties": false,
"jsonSerializerSettingsTransformationMethod": null,
"inlineNamedArrays": false,
"inlineNamedDictionaries": false,
"inlineNamedTuples": true,
"inlineNamedAny": false,
"generateDtoTypes": true,
"generateOptionalPropertiesAsNullable": false,
"generateNullableReferenceTypes": false,
"templateDirectory": null,
"typeNameGeneratorType": null,
"propertyNameGeneratorType": null,
"enumNameGeneratorType": null,
"serviceHost": null,
"serviceSchemes": null,
"output": "Generated/ApiClient.cs"
},
"openApiToTypeScriptClient": {
"className": "{controller}Client",
"moduleName": "",
"namespace": "",
"typeScriptVersion": 4.3,
"template": "Angular",
"promiseType": "Promise",
"httpClass": "HttpClient",
"withCredentials": false,
"useSingletonProvider": false,
"injectionTokenType": "InjectionToken",
"rxJsVersion": 7.0,
"dateTimeType": "Date",
"nullValue": "Null",
"generateClientClasses": true,
"generateClientInterfaces": false,
"generateOptionalParameters": false,
"exportTypes": true,
"wrapDtoExceptions": false,
"exceptionClass": "SwaggerException",
"clientBaseClass": null,
"wrapResponses": false,
"wrapResponseMethods": [],
"generateResponseClasses": true,
"respo