@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
JavaScript
"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>(