UNPKG

@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,538 lines (1,309 loc) 103 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.aspnetDapperTemplate = void 0; exports.aspnetDapperTemplate = { id: 'aspnet-dapper', name: 'aspnet-dapper', displayName: 'ASP.NET Core with Dapper', description: 'High-performance .NET API with Dapper micro-ORM', language: 'csharp', framework: 'aspnet-dapper', version: '1.0.0', tags: ['aspnet', 'dapper', 'micro-orm', 'performance', 'sql'], port: 5000, dependencies: {}, features: ['authentication', 'database', 'caching', 'logging', 'monitoring', 'testing'], files: { // Project file '{{serviceName}}.csproj': `<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> <ItemGroup> <PackageReference Include="Dapper" Version="2.1.24" /> <PackageReference Include="Dapper.Contrib" Version="2.0.78" /> <PackageReference Include="System.Data.SqlClient" Version="4.8.5" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" /> <PackageReference Include="Npgsql" Version="8.0.0" /> <PackageReference Include="MySql.Data" Version="8.2.0" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.SqlServer" Version="8.0.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" /> <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="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" /> <PackageReference Include="StackExchange.Redis" Version="2.7.10" /> <PackageReference Include="Polly" Version="8.2.0" /> <PackageReference Include="Polly.Extensions.Http" Version="3.0.0" /> </ItemGroup> </Project>`, // Program.cs 'Program.cs': `using {{serviceName}}.Data; using {{serviceName}}.Services; using {{serviceName}}.Models; using {{serviceName}}.Repositories; using {{serviceName}}.Configuration; using Microsoft.Data.SqlClient; using System.Data; using Serilog; using AutoMapper; using FluentValidation; using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Polly; using Polly.Extensions.Http; 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(); builder.Services.AddEndpointsApiExplorer(); // Configure Swagger/OpenAPI builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "{{serviceName}} API", Version = "v1", Description = "High-performance API built with ASP.NET Core and Dapper", Contact = new OpenApiContact { Name = "{{serviceName}} Team", Email = "team@{{serviceName}}.com" } }); // Include XML comments var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); // JWT Bearer configuration c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header using the Bearer scheme", 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>() } }); }); // Configure Database Connection builder.Services.AddScoped<IDbConnection>(sp => { var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); return new SqlConnection(connectionString); }); // Configure Redis (optional) if (!string.IsNullOrEmpty(builder.Configuration.GetConnectionString("Redis"))) { builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); }); } else { builder.Services.AddMemoryCache(); } // Configure AutoMapper builder.Services.AddAutoMapper(typeof(Program)); // Configure FluentValidation builder.Services.AddValidatorsFromAssemblyContaining<Program>(); // Configure JWT Authentication var jwtSettings = builder.Configuration.GetSection("JwtSettings"); var secretKey = jwtSettings["SecretKey"]; 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!)) }; }); builder.Services.AddAuthorization(); // Configure HTTP Client with Polly builder.Services.AddHttpClient("retryClient") .AddPolicyHandler(GetRetryPolicy()); // Register repositories and services builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IProductRepository, ProductRepository>(); builder.Services.AddScoped<IOrderRepository, OrderRepository>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddScoped<IOrderService, OrderService>(); builder.Services.AddScoped<IAuthService, AuthService>(); // Configure Database Initialization builder.Services.AddScoped<IDatabaseInitializer, DatabaseInitializer>(); // Add health checks builder.Services.AddHealthChecks() .AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!, name: "database"); // Configure CORS builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "{{serviceName}} API v1"); c.RoutePrefix = string.Empty; // Serve Swagger UI at root }); } app.UseHttpsRedirection(); app.UseCors("AllowAll"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks("/health"); // Initialize database using (var scope = app.Services.CreateScope()) { var dbInitializer = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>(); await dbInitializer.InitializeAsync(); } app.Run(); static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); }`, // Models 'Models/User.cs': `using System.ComponentModel.DataAnnotations; using Dapper.Contrib.Extensions; namespace {{serviceName}}.Models; [Table("Users")] public class User { [Key] public int Id { get; set; } [Required] [MaxLength(100)] public string FirstName { get; set; } = string.Empty; [Required] [MaxLength(100)] public string LastName { get; set; } = string.Empty; [Required] [EmailAddress] [MaxLength(200)] public string Email { get; set; } = string.Empty; [Phone] [MaxLength(20)] public string? Phone { get; set; } [MaxLength(255)] public string? PasswordHash { get; set; } public bool IsActive { get; set; } = true; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; [Computed] public string FullName => $"{FirstName} {LastName}".Trim(); }`, 'Models/Product.cs': `using System.ComponentModel.DataAnnotations; using Dapper.Contrib.Extensions; namespace {{serviceName}}.Models; [Table("Products")] public class Product { [Key] public int Id { get; set; } [Required] [MaxLength(200)] public string Name { get; set; } = string.Empty; [MaxLength(1000)] public string? Description { get; set; } public decimal Price { get; set; } [Required] [MaxLength(100)] public string Category { get; set; } = string.Empty; public int StockQuantity { get; set; } [MaxLength(50)] public string? SKU { get; set; } public bool IsActive { get; set; } = true; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; }`, 'Models/Order.cs': `using System.ComponentModel.DataAnnotations; using Dapper.Contrib.Extensions; namespace {{serviceName}}.Models; [Table("Orders")] public class Order { [Key] public int Id { get; set; } public int UserId { get; set; } [Required] [MaxLength(50)] public string OrderNumber { get; set; } = string.Empty; public decimal TotalAmount { get; set; } [Required] [MaxLength(50)] public string Status { get; set; } = "Pending"; public DateTime OrderDate { get; set; } = DateTime.UtcNow; public DateTime? ShippedDate { get; set; } public DateTime? DeliveredDate { get; set; } [MaxLength(500)] public string? ShippingAddress { get; set; } [MaxLength(1000)] public string? Notes { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; [Computed] public User? User { get; set; } [Computed] public List<OrderItem> Items { get; set; } = new(); }`, 'Models/OrderItem.cs': `using System.ComponentModel.DataAnnotations; using Dapper.Contrib.Extensions; namespace {{serviceName}}.Models; [Table("OrderItems")] public class OrderItem { [Key] public int Id { get; set; } public int OrderId { get; set; } public int ProductId { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal TotalPrice { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; [Computed] public Product? Product { get; set; } }`, // DTOs 'DTOs/UserDto.cs': `namespace {{serviceName}}.DTOs; public class UserDto { public int Id { get; set; } public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string? Phone { get; set; } public bool IsActive { get; set; } public DateTime CreatedAt { get; set; } public string FullName { get; set; } = string.Empty; } public class CreateUserDto { public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string? Phone { get; set; } public string Password { get; set; } = string.Empty; } public class UpdateUserDto { public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string? Phone { get; set; } public bool IsActive { get; set; } }`, 'DTOs/ProductDto.cs': `namespace {{serviceName}}.DTOs; public class ProductDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } = string.Empty; public int StockQuantity { get; set; } public string? SKU { get; set; } public bool IsActive { get; set; } public DateTime CreatedAt { get; set; } } public class CreateProductDto { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } = string.Empty; public int StockQuantity { get; set; } public string? SKU { get; set; } } public class UpdateProductDto { public string Name { get; set; } = string.Empty; public string? Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } = string.Empty; public int StockQuantity { get; set; } public string? SKU { get; set; } public bool IsActive { get; set; } }`, 'DTOs/OrderDto.cs': `namespace {{serviceName}}.DTOs; public class OrderDto { public int Id { get; set; } public int UserId { get; set; } public string OrderNumber { get; set; } = string.Empty; public decimal TotalAmount { get; set; } public string Status { get; set; } = string.Empty; public DateTime OrderDate { get; set; } public DateTime? ShippedDate { get; set; } public DateTime? DeliveredDate { get; set; } public string? ShippingAddress { get; set; } public string? Notes { get; set; } public UserDto? User { get; set; } public List<OrderItemDto> Items { get; set; } = new(); } public class CreateOrderDto { public int UserId { get; set; } public string? ShippingAddress { get; set; } public string? Notes { get; set; } public List<CreateOrderItemDto> Items { get; set; } = new(); } public class OrderItemDto { public int Id { get; set; } public int ProductId { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal TotalPrice { get; set; } public ProductDto? Product { get; set; } } public class CreateOrderItemDto { public int ProductId { get; set; } public int Quantity { get; set; } }`, 'DTOs/AuthDto.cs': `namespace {{serviceName}}.DTOs; public class LoginDto { public string Email { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; } public class AuthResponseDto { public string Token { get; set; } = string.Empty; public DateTime Expires { get; set; } public UserDto User { get; set; } = null!; }`, // Repositories 'Repositories/IUserRepository.cs': `using {{serviceName}}.Models; namespace {{serviceName}}.Repositories; public interface IUserRepository { Task<User?> GetByIdAsync(int id); Task<User?> GetByEmailAsync(string email); Task<IEnumerable<User>> GetAllAsync(); Task<IEnumerable<User>> GetPagedAsync(int page, int pageSize); Task<int> GetCountAsync(); Task<int> CreateAsync(User user); Task<bool> UpdateAsync(User user); Task<bool> DeleteAsync(int id); Task<bool> ExistsAsync(int id); Task<bool> EmailExistsAsync(string email, int? excludeId = null); }`, 'Repositories/UserRepository.cs': `using {{serviceName}}.Models; using {{serviceName}}.Repositories; using Dapper; using Dapper.Contrib.Extensions; using System.Data; namespace {{serviceName}}.Repositories; public class UserRepository : IUserRepository { private readonly IDbConnection _connection; private readonly ILogger<UserRepository> _logger; public UserRepository(IDbConnection connection, ILogger<UserRepository> logger) { _connection = connection; _logger = logger; } public async Task<User?> GetByIdAsync(int id) { try { return await _connection.GetAsync<User>(id); } catch (Exception ex) { _logger.LogError(ex, "Error getting user by ID: {UserId}", id); throw; } } public async Task<User?> GetByEmailAsync(string email) { try { const string sql = "SELECT * FROM Users WHERE Email = @Email AND IsActive = 1"; return await _connection.QueryFirstOrDefaultAsync<User>(sql, new { Email = email }); } catch (Exception ex) { _logger.LogError(ex, "Error getting user by email: {Email}", email); throw; } } public async Task<IEnumerable<User>> GetAllAsync() { try { const string sql = "SELECT * FROM Users WHERE IsActive = 1 ORDER BY FirstName, LastName"; return await _connection.QueryAsync<User>(sql); } catch (Exception ex) { _logger.LogError(ex, "Error getting all users"); throw; } } public async Task<IEnumerable<User>> GetPagedAsync(int page, int pageSize) { try { var offset = (page - 1) * pageSize; const string sql = @" SELECT * FROM Users WHERE IsActive = 1 ORDER BY FirstName, LastName OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"; return await _connection.QueryAsync<User>(sql, new { Offset = offset, PageSize = pageSize }); } catch (Exception ex) { _logger.LogError(ex, "Error getting paged users: Page {Page}, PageSize {PageSize}", page, pageSize); throw; } } public async Task<int> GetCountAsync() { try { const string sql = "SELECT COUNT(*) FROM Users WHERE IsActive = 1"; return await _connection.QuerySingleAsync<int>(sql); } catch (Exception ex) { _logger.LogError(ex, "Error getting user count"); throw; } } public async Task<int> CreateAsync(User user) { try { user.CreatedAt = DateTime.UtcNow; user.UpdatedAt = DateTime.UtcNow; const string sql = @" INSERT INTO Users (FirstName, LastName, Email, Phone, PasswordHash, IsActive, CreatedAt, UpdatedAt) VALUES (@FirstName, @LastName, @Email, @Phone, @PasswordHash, @IsActive, @CreatedAt, @UpdatedAt); SELECT CAST(SCOPE_IDENTITY() as int);"; var id = await _connection.QuerySingleAsync<int>(sql, user); user.Id = id; _logger.LogInformation("Created user: {UserId} - {Email}", id, user.Email); return id; } catch (Exception ex) { _logger.LogError(ex, "Error creating user: {Email}", user.Email); throw; } } public async Task<bool> UpdateAsync(User user) { try { user.UpdatedAt = DateTime.UtcNow; const string sql = @" UPDATE Users SET FirstName = @FirstName, LastName = @LastName, Email = @Email, Phone = @Phone, IsActive = @IsActive, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, user); _logger.LogInformation("Updated user: {UserId}", user.Id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error updating user: {UserId}", user.Id); throw; } } public async Task<bool> DeleteAsync(int id) { try { const string sql = "UPDATE Users SET IsActive = 0, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, new { Id = id, UpdatedAt = DateTime.UtcNow }); _logger.LogInformation("Soft deleted user: {UserId}", id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error deleting user: {UserId}", id); throw; } } public async Task<bool> ExistsAsync(int id) { try { const string sql = "SELECT COUNT(*) FROM Users WHERE Id = @Id AND IsActive = 1"; var count = await _connection.QuerySingleAsync<int>(sql, new { Id = id }); return count > 0; } catch (Exception ex) { _logger.LogError(ex, "Error checking if user exists: {UserId}", id); throw; } } public async Task<bool> EmailExistsAsync(string email, int? excludeId = null) { try { var sql = "SELECT COUNT(*) FROM Users WHERE Email = @Email AND IsActive = 1"; var parameters = new { Email = email }; if (excludeId.HasValue) { sql += " AND Id != @ExcludeId"; parameters = new { Email = email, ExcludeId = excludeId.Value }; } var count = await _connection.QuerySingleAsync<int>(sql, parameters); return count > 0; } catch (Exception ex) { _logger.LogError(ex, "Error checking if email exists: {Email}", email); throw; } } }`, 'Repositories/IProductRepository.cs': `using {{serviceName}}.Models; namespace {{serviceName}}.Repositories; public interface IProductRepository { Task<Product?> GetByIdAsync(int id); Task<Product?> GetBySKUAsync(string sku); Task<IEnumerable<Product>> GetAllAsync(); Task<IEnumerable<Product>> GetPagedAsync(int page, int pageSize, string? category = null, string? search = null); Task<IEnumerable<Product>> GetByCategoryAsync(string category); Task<int> GetCountAsync(string? category = null, string? search = null); Task<int> CreateAsync(Product product); Task<bool> UpdateAsync(Product product); Task<bool> DeleteAsync(int id); Task<bool> UpdateStockAsync(int id, int quantity); Task<bool> ExistsAsync(int id); }`, 'Repositories/ProductRepository.cs': `using {{serviceName}}.Models; using {{serviceName}}.Repositories; using Dapper; using System.Data; using System.Text; namespace {{serviceName}}.Repositories; public class ProductRepository : IProductRepository { private readonly IDbConnection _connection; private readonly ILogger<ProductRepository> _logger; public ProductRepository(IDbConnection connection, ILogger<ProductRepository> logger) { _connection = connection; _logger = logger; } public async Task<Product?> GetByIdAsync(int id) { try { const string sql = "SELECT * FROM Products WHERE Id = @Id AND IsActive = 1"; return await _connection.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id }); } catch (Exception ex) { _logger.LogError(ex, "Error getting product by ID: {ProductId}", id); throw; } } public async Task<Product?> GetBySKUAsync(string sku) { try { const string sql = "SELECT * FROM Products WHERE SKU = @SKU AND IsActive = 1"; return await _connection.QueryFirstOrDefaultAsync<Product>(sql, new { SKU = sku }); } catch (Exception ex) { _logger.LogError(ex, "Error getting product by SKU: {SKU}", sku); throw; } } public async Task<IEnumerable<Product>> GetAllAsync() { try { const string sql = "SELECT * FROM Products WHERE IsActive = 1 ORDER BY Name"; return await _connection.QueryAsync<Product>(sql); } catch (Exception ex) { _logger.LogError(ex, "Error getting all products"); throw; } } public async Task<IEnumerable<Product>> GetPagedAsync(int page, int pageSize, string? category = null, string? search = null) { try { var offset = (page - 1) * pageSize; var sqlBuilder = new StringBuilder(@" SELECT * FROM Products WHERE IsActive = 1"); var parameters = new DynamicParameters(); parameters.Add("Offset", offset); parameters.Add("PageSize", pageSize); if (!string.IsNullOrEmpty(category)) { sqlBuilder.Append(" AND Category = @Category"); parameters.Add("Category", category); } if (!string.IsNullOrEmpty(search)) { sqlBuilder.Append(" AND (Name LIKE @Search OR Description LIKE @Search)"); parameters.Add("Search", $"%{search}%"); } sqlBuilder.Append(" ORDER BY Name OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"); return await _connection.QueryAsync<Product>(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error getting paged products: Page {Page}, PageSize {PageSize}", page, pageSize); throw; } } public async Task<IEnumerable<Product>> GetByCategoryAsync(string category) { try { const string sql = "SELECT * FROM Products WHERE Category = @Category AND IsActive = 1 ORDER BY Name"; return await _connection.QueryAsync<Product>(sql, new { Category = category }); } catch (Exception ex) { _logger.LogError(ex, "Error getting products by category: {Category}", category); throw; } } public async Task<int> GetCountAsync(string? category = null, string? search = null) { try { var sqlBuilder = new StringBuilder("SELECT COUNT(*) FROM Products WHERE IsActive = 1"); var parameters = new DynamicParameters(); if (!string.IsNullOrEmpty(category)) { sqlBuilder.Append(" AND Category = @Category"); parameters.Add("Category", category); } if (!string.IsNullOrEmpty(search)) { sqlBuilder.Append(" AND (Name LIKE @Search OR Description LIKE @Search)"); parameters.Add("Search", $"%{search}%"); } return await _connection.QuerySingleAsync<int>(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error getting product count"); throw; } } public async Task<int> CreateAsync(Product product) { try { product.CreatedAt = DateTime.UtcNow; product.UpdatedAt = DateTime.UtcNow; const string sql = @" INSERT INTO Products (Name, Description, Price, Category, StockQuantity, SKU, IsActive, CreatedAt, UpdatedAt) VALUES (@Name, @Description, @Price, @Category, @StockQuantity, @SKU, @IsActive, @CreatedAt, @UpdatedAt); SELECT CAST(SCOPE_IDENTITY() as int);"; var id = await _connection.QuerySingleAsync<int>(sql, product); product.Id = id; _logger.LogInformation("Created product: {ProductId} - {Name}", id, product.Name); return id; } catch (Exception ex) { _logger.LogError(ex, "Error creating product: {Name}", product.Name); throw; } } public async Task<bool> UpdateAsync(Product product) { try { product.UpdatedAt = DateTime.UtcNow; const string sql = @" UPDATE Products SET Name = @Name, Description = @Description, Price = @Price, Category = @Category, StockQuantity = @StockQuantity, SKU = @SKU, IsActive = @IsActive, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, product); _logger.LogInformation("Updated product: {ProductId}", product.Id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error updating product: {ProductId}", product.Id); throw; } } public async Task<bool> DeleteAsync(int id) { try { const string sql = "UPDATE Products SET IsActive = 0, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, new { Id = id, UpdatedAt = DateTime.UtcNow }); _logger.LogInformation("Soft deleted product: {ProductId}", id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error deleting product: {ProductId}", id); throw; } } public async Task<bool> UpdateStockAsync(int id, int quantity) { try { const string sql = @" UPDATE Products SET StockQuantity = @Quantity, UpdatedAt = @UpdatedAt WHERE Id = @Id AND IsActive = 1"; var affected = await _connection.ExecuteAsync(sql, new { Id = id, Quantity = quantity, UpdatedAt = DateTime.UtcNow }); _logger.LogInformation("Updated stock for product {ProductId}: {Quantity}", id, quantity); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error updating stock for product: {ProductId}", id); throw; } } public async Task<bool> ExistsAsync(int id) { try { const string sql = "SELECT COUNT(*) FROM Products WHERE Id = @Id AND IsActive = 1"; var count = await _connection.QuerySingleAsync<int>(sql, new { Id = id }); return count > 0; } catch (Exception ex) { _logger.LogError(ex, "Error checking if product exists: {ProductId}", id); throw; } } }`, 'Repositories/IOrderRepository.cs': `using {{serviceName}}.Models; namespace {{serviceName}}.Repositories; public interface IOrderRepository { Task<Order?> GetByIdAsync(int id); Task<Order?> GetByOrderNumberAsync(string orderNumber); Task<IEnumerable<Order>> GetByUserIdAsync(int userId); Task<IEnumerable<Order>> GetPagedAsync(int page, int pageSize, string? status = null); Task<int> GetCountAsync(string? status = null); Task<int> CreateAsync(Order order); Task<bool> UpdateAsync(Order order); Task<bool> UpdateStatusAsync(int id, string status); Task<bool> DeleteAsync(int id); Task<List<OrderItem>> GetOrderItemsAsync(int orderId); Task<int> CreateOrderItemAsync(OrderItem orderItem); }`, 'Repositories/OrderRepository.cs': `using {{serviceName}}.Models; using {{serviceName}}.Repositories; using Dapper; using System.Data; using System.Text; namespace {{serviceName}}.Repositories; public class OrderRepository : IOrderRepository { private readonly IDbConnection _connection; private readonly ILogger<OrderRepository> _logger; public OrderRepository(IDbConnection connection, ILogger<OrderRepository> logger) { _connection = connection; _logger = logger; } public async Task<Order?> GetByIdAsync(int id) { try { const string sql = @" SELECT o.*, u.Id, u.FirstName, u.LastName, u.Email, u.Phone FROM Orders o INNER JOIN Users u ON o.UserId = u.Id WHERE o.Id = @Id"; var orderDict = new Dictionary<int, Order>(); var orders = await _connection.QueryAsync<Order, User, Order>( sql, (order, user) => { if (!orderDict.TryGetValue(order.Id, out var orderEntry)) { orderEntry = order; orderEntry.User = user; orderDict.Add(order.Id, orderEntry); } return orderEntry; }, new { Id = id }, splitOn: "Id"); var result = orders.FirstOrDefault(); if (result != null) { result.Items = await GetOrderItemsAsync(result.Id); } return result; } catch (Exception ex) { _logger.LogError(ex, "Error getting order by ID: {OrderId}", id); throw; } } public async Task<Order?> GetByOrderNumberAsync(string orderNumber) { try { const string sql = "SELECT * FROM Orders WHERE OrderNumber = @OrderNumber"; var order = await _connection.QueryFirstOrDefaultAsync<Order>(sql, new { OrderNumber = orderNumber }); if (order != null) { order.Items = await GetOrderItemsAsync(order.Id); } return order; } catch (Exception ex) { _logger.LogError(ex, "Error getting order by number: {OrderNumber}", orderNumber); throw; } } public async Task<IEnumerable<Order>> GetByUserIdAsync(int userId) { try { const string sql = "SELECT * FROM Orders WHERE UserId = @UserId ORDER BY OrderDate DESC"; var orders = await _connection.QueryAsync<Order>(sql, new { UserId = userId }); foreach (var order in orders) { order.Items = await GetOrderItemsAsync(order.Id); } return orders; } catch (Exception ex) { _logger.LogError(ex, "Error getting orders by user ID: {UserId}", userId); throw; } } public async Task<IEnumerable<Order>> GetPagedAsync(int page, int pageSize, string? status = null) { try { var offset = (page - 1) * pageSize; var sqlBuilder = new StringBuilder("SELECT * FROM Orders WHERE 1=1"); var parameters = new DynamicParameters(); parameters.Add("Offset", offset); parameters.Add("PageSize", pageSize); if (!string.IsNullOrEmpty(status)) { sqlBuilder.Append(" AND Status = @Status"); parameters.Add("Status", status); } sqlBuilder.Append(" ORDER BY OrderDate DESC OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"); return await _connection.QueryAsync<Order>(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error getting paged orders: Page {Page}, PageSize {PageSize}", page, pageSize); throw; } } public async Task<int> GetCountAsync(string? status = null) { try { var sqlBuilder = new StringBuilder("SELECT COUNT(*) FROM Orders WHERE 1=1"); var parameters = new DynamicParameters(); if (!string.IsNullOrEmpty(status)) { sqlBuilder.Append(" AND Status = @Status"); parameters.Add("Status", status); } return await _connection.QuerySingleAsync<int>(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _logger.LogError(ex, "Error getting order count"); throw; } } public async Task<int> CreateAsync(Order order) { try { order.CreatedAt = DateTime.UtcNow; order.UpdatedAt = DateTime.UtcNow; order.OrderDate = DateTime.UtcNow; const string sql = @" INSERT INTO Orders (UserId, OrderNumber, TotalAmount, Status, OrderDate, ShippingAddress, Notes, CreatedAt, UpdatedAt) VALUES (@UserId, @OrderNumber, @TotalAmount, @Status, @OrderDate, @ShippingAddress, @Notes, @CreatedAt, @UpdatedAt); SELECT CAST(SCOPE_IDENTITY() as int);"; var id = await _connection.QuerySingleAsync<int>(sql, order); order.Id = id; _logger.LogInformation("Created order: {OrderId} - {OrderNumber}", id, order.OrderNumber); return id; } catch (Exception ex) { _logger.LogError(ex, "Error creating order: {OrderNumber}", order.OrderNumber); throw; } } public async Task<bool> UpdateAsync(Order order) { try { order.UpdatedAt = DateTime.UtcNow; const string sql = @" UPDATE Orders SET TotalAmount = @TotalAmount, Status = @Status, ShippedDate = @ShippedDate, DeliveredDate = @DeliveredDate, ShippingAddress = @ShippingAddress, Notes = @Notes, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, order); _logger.LogInformation("Updated order: {OrderId}", order.Id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error updating order: {OrderId}", order.Id); throw; } } public async Task<bool> UpdateStatusAsync(int id, string status) { try { const string sql = "UPDATE Orders SET Status = @Status, UpdatedAt = @UpdatedAt WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, new { Id = id, Status = status, UpdatedAt = DateTime.UtcNow }); _logger.LogInformation("Updated order status: {OrderId} -> {Status}", id, status); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error updating order status: {OrderId}", id); throw; } } public async Task<bool> DeleteAsync(int id) { try { const string sql = "DELETE FROM Orders WHERE Id = @Id"; var affected = await _connection.ExecuteAsync(sql, new { Id = id }); _logger.LogInformation("Deleted order: {OrderId}", id); return affected > 0; } catch (Exception ex) { _logger.LogError(ex, "Error deleting order: {OrderId}", id); throw; } } public async Task<List<OrderItem>> GetOrderItemsAsync(int orderId) { try { const string sql = @" SELECT oi.*, p.Id, p.Name, p.Description, p.Price, p.Category, p.SKU FROM OrderItems oi INNER JOIN Products p ON oi.ProductId = p.Id WHERE oi.OrderId = @OrderId"; var orderItemDict = new Dictionary<int, OrderItem>(); var orderItems = await _connection.QueryAsync<OrderItem, Product, OrderItem>( sql, (orderItem, product) => { if (!orderItemDict.TryGetValue(orderItem.Id, out var orderItemEntry)) { orderItemEntry = orderItem; orderItemEntry.Product = product; orderItemDict.Add(orderItem.Id, orderItemEntry); } return orderItemEntry; }, new { OrderId = orderId }, splitOn: "Id"); return orderItems.ToList(); } catch (Exception ex) { _logger.LogError(ex, "Error getting order items for order: {OrderId}", orderId); throw; } } public async Task<int> CreateOrderItemAsync(OrderItem orderItem) { try { orderItem.CreatedAt = DateTime.UtcNow; const string sql = @" INSERT INTO OrderItems (OrderId, ProductId, Quantity, UnitPrice, TotalPrice, CreatedAt) VALUES (@OrderId, @ProductId, @Quantity, @UnitPrice, @TotalPrice, @CreatedAt); SELECT CAST(SCOPE_IDENTITY() as int);"; var id = await _connection.QuerySingleAsync<int>(sql, orderItem); orderItem.Id = id; return id; } catch (Exception ex) { _logger.LogError(ex, "Error creating order item for order: {OrderId}", orderItem.OrderId); throw; } } }`, // Services 'Services/IUserService.cs': `using {{serviceName}}.DTOs; namespace {{serviceName}}.Services; public interface IUserService { Task<UserDto?> GetByIdAsync(int id); Task<UserDto?> GetByEmailAsync(string email); Task<IEnumerable<UserDto>> GetAllAsync(); Task<(IEnumerable<UserDto> Users, int TotalCount)> GetPagedAsync(int page, int pageSize); Task<UserDto> CreateAsync(CreateUserDto createUserDto); Task<UserDto?> UpdateAsync(int id, UpdateUserDto updateUserDto); Task<bool> DeleteAsync(int id); Task<bool> ExistsAsync(int id); }`, 'Services/UserService.cs': `using {{serviceName}}.DTOs; using {{serviceName}}.Models; using {{serviceName}}.Repositories; using AutoMapper; using BCrypt.Net; namespace {{serviceName}}.Services; public class UserService : IUserService { private readonly IUserRepository _userRepository; private readonly IMapper _mapper; private readonly ILogger<UserService> _logger; public UserService(IUserRepository userRepository, IMapper mapper, ILogger<UserService> logger) { _userRepository = userRepository; _mapper = mapper; _logger = logger; } public async Task<UserDto?> GetByIdAsync(int id) { var user = await _userRepository.GetByIdAsync(id); return user == null ? null : _mapper.Map<UserDto>(user); } public async Task<UserDto?> GetByEmailAsync(string email) { var user = await _userRepository.GetByEmailAsync(email); return user == null ? null : _mapper.Map<UserDto>(user); } public async Task<IEnumerable<UserDto>> GetAllAsync() { var users = await _userRepository.GetAllAsync(); return _mapper.Map<IEnumerable<UserDto>>(users); } public async Task<(IEnumerable<UserDto> Users, int TotalCount)> GetPagedAsync(int page, int pageSize) { var users = await _userRepository.GetPagedAsync(page, pageSize); var totalCount = await _userRepository.GetCountAsync(); return (_mapper.Map<IEnumerable<UserDto>>(users), totalCount); } public async Task<UserDto> CreateAsync(CreateUserDto createUserDto) { // Check if email already exists if (await _userRepository.EmailExistsAsync(createUserDto.Email)) { throw new InvalidOperationException($"User with email {createUserDto.Email} already exists"); } var user = _mapper.Map<User>(createUserDto); user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(createUserDto.Password); await _userRepository.CreateAsync(user); _logger.LogInformation("Created user: {UserId} - {Email}", user.Id, user.Email); return _mapper.Map<UserDto>(user); } public async Task<UserDto?> UpdateAsync(int id, UpdateUserDto updateUserDto) { var existingUser = await _userRepository.GetByIdAsync(id); if (existingUser == null) { return null; } // Check if email already exists for another user if (await _userRepository.EmailExistsAsync(updateUserDto.Email, id)) { throw new InvalidOperationException($"User with email {updateUserDto.Email} already exists"); } _mapper.Map(updateUserDto, existingUser); await _userRepository.UpdateAsync(existingUser); _logger.LogInformation("Updated user: {UserId}", id); return _mapper.Map<UserDto>(existingUser); } public async Task<bool> DeleteAsync(int id) { if (!await _userRepository.ExistsAsync(id)) { return false; } var result = await _userRepository.DeleteAsync(id); if (result) { _logger.LogInformation("Deleted user: {UserId}", id); } return result; } public async Task<bool> ExistsAsync(int id) { return await _userRepository.ExistsAsync(id); } }`, 'Services/IProductService.cs': `using {{serviceName}}.DTOs; namespace {{serviceName}}.Services; public interface IProductService { Task<ProductDto?> GetByIdAsync(int id); Task<ProductDto?> GetBySKUAsync(string sku); Task<IEnumerable<ProductDto>> GetAllAsync(); Task<(IEnumerable<ProductDto> Products, int TotalCount)> GetPagedAsync(int page, int pageSize, string? category = null, string? search = null); Task<IEnumerable<ProductDto>> GetByCategoryAsync(string category); Task<ProductDto> CreateAsync(CreateProductDto createProductDto); Task<ProductDto?> UpdateAsync(int id, UpdateProductDto updateProductDto); Task<bool> DeleteAsync(int id); Task<bool> UpdateStockAsync(int id, int quantity); Task<bool> ExistsAsync(int id); }`, 'Services/ProductService.cs': `using {{serviceName}}.DTOs; using {{serviceName}}.Models; using {{serviceName}}.Repositories; using AutoMapper; namespace {{serviceName}}.Services; public class ProductService : IProductService { private readonly IProductRepository _productRepository; private readonly IMapper _mapper; private readonly ILogger<ProductService> _logger; public ProductService(IProductRepository productRepository, IMapper mapper, ILogger<ProductService> logger) { _productRepository = productRepository; _mapper = mapper; _logger = logger; } public async Task<ProductDto?> GetByIdAsync(int id) { var product = await _productRepository.GetByIdAsync(id); return product == null ? null : _mapper.Map<ProductDto>(product); } public async Task<ProductDto?> GetBySKUAsync(string sku) { var product = await _productRepository.GetBySKUAsync(sku); return product == null ? null : _mapper.Map<ProductDto>(product); } public async Task<IEnumerable<ProductDto>> GetAllAsync() { var products = await _productRepository.GetAllAsync(); return _mapper.Map<IEnumerable<ProductDto>>(products); } public async Task<(IEnumerable<ProductDto> Products, int TotalCount)> GetPagedAsync(int page, int pageSize, string? category = null, string? search = null) { var products = await _productRepository.GetPagedAsync(page, pageSize, category, search); var totalCount = await _productRepository.GetCountAsync(category, search); return (_mapper.Map<IEnumerable<ProductDto>>(products), totalCount); } public async Task<IEnumerable<ProductDto>> GetByCategoryAsync(string category) { var products = await _productRepository.GetByCategoryAsync(category); return _mapper.Map<IEnumerable<ProductDto>>(products); } public async Task<ProductDto> CreateAsync(CreateProductDto createProductDto) { var product = _mapper.Map<Product>(createProductDto); // Generate SKU if not provided if (string.IsNullOrEmpty(product.SKU)) { product.SKU = GenerateSKU(product.Name, product.Category); } await _productRepository.CreateAsync(product); _logger.LogInformation("Created product: {ProductId} - {Name}", product.Id, product.Name); return _mapper.Map<ProductDto>(product); } public async Task<ProductDto?> UpdateAsync(int id, UpdateProductDto updateProductDto) { var existingProduct = await _productRepository.GetByIdAsync(id); if (existingProduct == null) { return null; } _mapper.Map(updateProductDto, existingProduct); await _productRepository.UpdateAsync(existingProduct); _logger.LogInformation("Updated product: {ProductId}", id); return _mapper.Map<ProductDto>(