INTRODUCTION
In today's digital era, e-commerce has become a vital component of the global economy, enabling businesses to reach a wider audience and streamline their operations. Developing a robust and scalable e-commerce application requires a thoughtful approach to architecture and technology stack. This technical writing delves into the creation of a furniture e-commerce application using the .NET Clean Architecture model and MongoDB as the database.
The .NET Clean Architecture provides a structured and organized way to build applications, promoting separation of concerns and maintainability. By leveraging the power of MongoDB, a NoSQL database known for its scalability and flexibility, we ensure that our furniture e-commerce application can handle a high volume of transactions and adapt to changing requirements.
This article will guide you through the key components and design principles of the .NET Clean Architecture, the integration of MongoDB, and the various features and functionalities of the furniture e-commerce application. Whether you are a developer looking to adopt best practices or a technical enthusiast interested in modern software development, this writing aims to provide comprehensive insights into building a successful e-commerce platform tailored for the furniture industry.
Setting Up the Project Structure for Lacariz Furniture E-commerce Application
In this section, we will guide you through the initial setup of a furniture e-commerce application using the .NET Clean Architecture model. This involves creating a Web API project along with three class libraries for data access, domain entities, and service logic. Follow the steps below to set up your project structure:
Step 1: Create the Web API Project
- Open Visual Studio.
- Select Create a new project.
- Choose ASP.NET Core Web API and click Next.
- Name the project Lacariz.Furniture.Api and choose a suitable location.
- Click Create and configure the project with the latest .NET version.
- Click Create again to generate the project.
Step 2: Create the Class Libraries
- In the Solution Explorer, right-click on the solution and select Add > New Project.
- Choose Class Library and click Next.
- Name the project Lacariz.Furniture.Data and click Create.
- Repeat the steps to create two more class libraries named Lacariz.Furniture.Domain and Lacariz.Furniture.Service.
Your solution should now contain the following projects:
- Lacariz.Furniture.Api
- Lacariz.Furniture.Data
- Lacariz.Furniture.Domain
- Lacariz.Furniture.Service
Project Structure Overview
The Lacariz.Furniture.Api
project will serve as the entry point of the application, exposing the Web API endpoints. The Lacariz.Furniture.Data
project will handle data access and communication with MongoDB
. The Lacariz.Furniture.Domain
project will contain the domain entities. Finally, the Lacariz.Furniture.Service
project will implement the service layer, containing the application’s use cases and business rules.
To help you set up your project structure, I have attached a screenshot guide illustrating the above steps. Ensure your solution explorer reflects the organization shown in the screenshot before proceeding to the implementation phase.
By following these steps, you will have a solid foundation for building a scalable and maintainable furniture e-commerce application using .NET Clean Architecture and MongoDB. In the subsequent sections, we will delve deeper into each project, exploring their roles and how they interact with each other to form a cohesive application.
Configuring MongoDB for Lacariz Furniture E-commerce Application
In this section, we will configure MongoDB
for the Lacariz Furniture e-commerce application. We'll create a configuration folder structure in the Lacariz.Furniture.Domain project, focusing on the MongoDB configuration. Follow these steps:
Step 1: Create the Config Folder Structure
Create the Config Folder:
In the Lacariz.Furniture.Domain project, add a new folder named Config.
Create Interfaces and Implementations Folders:
Inside the Config folder, create two subfolders named Interfaces and Implementations.
Step 2: Define MongoDB Configuration Interface
Add IMongoDbConfig Interface :
In the Interfaces folder, create an interface file named IMongoDbConfig.cs.
namespace Lacariz.Furniture.Domain.Config.Interfaces;
public partial interface IMongoDbConfig
{
string DatabaseName { get; set; }
string ConnectionString { get; set; }
}
Step 3: Implement MongoDB Configuration
Add MongoDbConfig Class:
In the Implementations folder, create a class file named MongoDbConfig.cs.
This class will implement the IMongoDbConfig interface, providing concrete properties for the MongoDB configuration.
using Lacariz.Furniture.Domain.Config.Interfaces;
namespace Lacariz.Furniture.Domain.Config.Implementations;
public partial class MongoDbConfig : IMongoDbConfig
{
public string DatabaseName { get; set; }
public string ConnectionString { get; set; }
public MongoDbConfig(string connectionString, string databaseName)
{
DatabaseName = databaseName;
ConnectionString = connectionString;
}
}
Step 4: Configuring the program.cs for ASP.NET Core Application
- Serilog Configuration:
Sets up Serilog for logging. It enriches logs with contextual information, writes logs to the console, and reads configuration settings.
- Swagger/OpenAPI Configuration:
Configures Swagger/OpenAPI to provide documentation and testing tools for the API endpoints.
- Health Checks:
Adds health check services to monitor the application's health status.
CORS Policy:
- Configures Cross-Origin Resource Sharing (CORS) policy to allow requests from any origin, method, and header, facilitating cross-domain communication.
- Configuration and Dependencies:
Loads additional configuration settings and registers data and service dependencies required by the application.
API Versioning:
- Configures API versioning to manage different versions of the API and maintain backward compatibility.
- Health Checks UI:
Configures the Health Checks UI to provide a dashboard for viewing health check results and sets up various parameters like evaluation time, maximum history entries, and API endpoint configuration.
Development Environment Configuration:
- Configures Swagger and Swagger UI to be available only in the development environment for easier API testing and documentation.
Logging, CORS, HTTPS Redirection, and Authorization:
Sets up request logging using Serilog, enables CORS policy, redirects HTTP requests to HTTPS for security, and sets up authorization middleware.
- Mapping Controllers and Health Checks:
Maps controller routes to handle API requests, configures Health Checks UI middleware for monitoring, and sets up the endpoint for health checks.
Running the Application:
- Starts the application, launching the web server and making it ready to handle incoming requests.
//COPY AND PASTE IN YOUR PROGRAM.CS FILE
using HealthChecks.UI.Client;
//using Lacariz.Brevort.API;
using Lacariz.Furniture.Data;
using Lacariz.Furniture.Service;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.OpenApi.Models;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Host.UseSerilog((context, config) =>
{
config.Enrich.FromLogContext()
.WriteTo.Console()
.ReadFrom.Configuration(context.Configuration);
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(
c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Lacariz.Furniture", Version = "v1" });
}
);
builder.Services.AddHealthChecks();
builder.Services.AddCors(p => p.AddPolicy("corsapp", builder =>
{
builder.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
}));
builder.AddConfiguration();
builder.Services.AddDataDependencies(builder.Configuration);
builder.Services.AddServiceDependencies(builder.Configuration);
builder.Services.AddApiVersioning(x =>
{
x.DefaultApiVersion = new ApiVersion(1, 0);
x.AssumeDefaultVersionWhenUnspecified = true;
x.ReportApiVersions = true;
});
builder.Services.AddHealthChecksUI(opt =>
{
opt.SetEvaluationTimeInSeconds(builder.Configuration.GetValue<int>("HealthCheckConfig:EvaluationTimeInSeconds")); //time in seconds between check
opt.MaximumHistoryEntriesPerEndpoint(builder.Configuration.GetValue<int>("HealthCheckConfig:MaxHistoryPerEndpoint")); //maximum history of checks
opt.SetApiMaxActiveRequests(builder.Configuration.GetValue<int>("HealthCheckConfig:ApiMaxActiveRequest")); //api requests concurrency
opt.AddHealthCheckEndpoint("default api", builder.Configuration.GetValue<string>("HealthCheckConfig:HealthCheckEndpoint")); //map health check api
//bypass ssl
opt.UseApiEndpointHttpMessageHandler(sp =>
{
return new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => { return true; }
};
});
})
.AddInMemoryStorage();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(x =>
{
x.SwaggerEndpoint("/swagger/v1/swagger.json", "Lacariz.CharisHome");
x.RoutePrefix = string.Empty;
});
}
//app.UseMiddleware<EncryptionMiddleware>();
app.UseSerilogRequestLogging();
app.UseCors("corsapp");
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseHealthChecksUI();
app.MapHealthChecks(builder.Configuration.GetValue<string>("HealthCheckConfig:HealthCheckEndpoint"), new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.Run();
Update appsettings.json:
Ensure the appsettings.json file includes a section for MongoDbConfig, specifying the DatabaseName and ConnectionString and other information that will be needed throughout the application.
{
"MongoDbSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "LacarizFurnitureDB"
},
"JwtConfig": {
"Secret": "xsxderyrredfghjkllknnnmuyffyuvhhgfhhjhgfuytrsewsdfwsdftfuhioikpoijiughtcgredwsxfedcvhgbiuhmkoiokpokjmkhngbgffghert",
"Issuer": "http://localhost:5013"
},
"EmailSettings": {
"SmtpHost": "smtp.elasticemail.com",
"SmtpPort": "2525",
"SmtpUser": "--Your email address--",
"SmtpPass": "--your smtp elastic mail pass--"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
},
"PollyConfig": {
"BreakerTime": 2,
"RetryTime": 1,
"RetryCount": 5,
"HandledEventsAllowedBeforeBreaking": 5
},
"HealthCheckConfig": {
"EvaluationTimeInSeconds": 18000,
"MaxHistoryPerEndpoint": 50,
"ApiMaxActiveRequest": 1,
"HealthCheckEndpoint": "/health"
},
"PushNotification": {
"type": "service_account",
"project_id": "lacariz-furniture",
"private_key_id": "89b19620f584e5ba541b35210089eaf2e9545c0b",
"private_key": "--Your private key--",
"client_email": "--Your client email--",
"client_id": "--Your client Id--",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "--your certificate url--",
"universe_domain": "googleapis.com"
}
}
Summary
By following these steps, you will set up a clear and organized configuration for MongoDB
within your Lacariz Furniture e-commerce application
. This includes creating the necessary folder structure for configuration interfaces and implementations, defining the required configuration interface, and implementing the configuration class. Additionally, you will register the configuration in the API project's program class and update the application settings to include necessary details. This structured approach ensures maintainability and scalability for your application's configuration.
Configuring MongoDB Interface and Repository for Lacariz Furniture E-commerce Application
Before we implement the notification service, we need to set up the MongoDB interface and repository that will handle communication with the database
public partial interface IMongoDBLogContext
{
IMongoCollection<MyBankLog> Logs { get; set; }
IMongoCollection<User> Users { get; set; }
IMongoCollection<Admin> Admins { get; set; }
IMongoCollection<FurnitureItem> FurnitureItems { get; set; }
IMongoCollection<ShoppingCart> ShoppingCarts { get; set; }
IMongoCollection<WishlistItem> WishlistItems { get; set; }
IMongoCollection<Order> Orders { get; set; }
IMongoCollection<PreOrder> PreOrders { get; set; }
IMongoCollection<CustomerInquiry> CustomerInquiries { get; set; }
}
public partial class MongoDBLogContext : IMongoDBLogContext
{
public IMongoCollection<MyBankLog> Logs { get; set; }
public IMongoCollection<User> Users { get; set; }
public IMongoCollection<Admin> Admins { get; set; }
public IMongoCollection<FurnitureItem> FurnitureItems { get; set; }
public IMongoCollection<ShoppingCart> ShoppingCarts { get; set; }
public IMongoCollection<WishlistItem> WishlistItems { get; set ; }
public IMongoCollection<Order> Orders { get; set; }
public IMongoCollection<PreOrder> PreOrders { get; set; }
public IMongoCollection<CustomerInquiry> CustomerInquiries { get; set; }
public MongoDBLogContext(IMongoDbConfig config, IMongoClient mongoClient)
{
var client = new MongoClient(config.ConnectionString);
var database = client.GetDatabase(config.DatabaseName);
Logs = database.GetCollection<MyBankLog>("MyBankLog");
Users = database.GetCollection<User>("User");
Admins = database.GetCollection<Admin>("Admin");
FurnitureItems = database.GetCollection<FurnitureItem>("FurnitureItem");
ShoppingCarts = database.GetCollection<ShoppingCart>("ShoppingCartItem");
WishlistItems = database.GetCollection<WishlistItem>("WishlistItem");
Orders = database.GetCollection<Order>("Order");
PreOrders = database.GetCollection<PreOrder>("PreOrder");
CustomerInquiries = database.GetCollection<CustomerInquiry>("CustomerInquiry");
}
}
Implementing the Notification Service for Lacariz Furniture E-commerce Application
In this section, we will implement the notification service, starting from the repository interface and implementation.
using Lacariz.Furniture.Domain.Entities;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IUserRepository
{
Task<User> RegisterUser(User user);
Task<User> GetUserByEmail(string emailAddress);
Task<User> GetUserById(string userId);
Task<bool> EmailExists(string emailAddress);
Task ResetPassword(string emailAddress, string newPassword);
Task UpdateUserActivationStatus(string emailAddress, bool isActivated);
Task UpdateAdminActivationStatus(string emailAddress, bool isActivated);
Task<Admin> RegisterAdmin(Admin admin);
Task<Admin> GetAdminByEmail(string emailAddress);
Task<Admin> GetAdminByLoginId(string loginId);
}
}
User Repository Implementation
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
using System.Net.Mail;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class UserRepository : IUserRepository
{
// private readonly IMySqlDbContext mySqlDbContext;
private readonly IMongoDBLogContext dbContext;
public UserRepository(/*IMySqlDbContext mySqlDbContext*/ IMongoDBLogContext dbContext)
{
// this.mySqlDbContext = mySqlDbContext;
this.dbContext = dbContext;
}
public async Task<bool> EmailExists(string emailAddress)
{
var filter = Builders<User>.Filter.Eq(u => u.EmailAddress, emailAddress);
var user = await dbContext.Users.Find(filter).FirstOrDefaultAsync();
return user != null;
}
public async Task<Admin> GetAdminByEmail(string emailAddress)
{
try
{
return await dbContext.Admins.Find(u => u.EmailAddress == emailAddress).FirstOrDefaultAsync();
}
catch (Exception)
{
throw;
}
}
public async Task<Admin> GetAdminByLoginId(string loginId)
{
try
{
return await dbContext.Admins.Find(u => u.AdminLoginId == loginId).FirstOrDefaultAsync();
}
catch (Exception)
{
throw;
}
}
public async Task<User> GetUserByEmail(string emailAddress)
{
try
{
return await dbContext.Users.Find(u => u.EmailAddress == emailAddress).FirstOrDefaultAsync();
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<User> GetUserById(string userId)
{
try
{
return await dbContext.Users.Find(u => u.Id == userId).FirstOrDefaultAsync();
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<Admin> RegisterAdmin(Admin admin)
{
try
{
await dbContext.Admins.InsertOneAsync(admin);
return admin;
}
catch (Exception)
{
throw;
}
}
public async Task<User> RegisterUser(User user)
{
try
{
await dbContext.Users.InsertOneAsync(user);
return user;
// await mySqlDbContext.SaveChangesAsync();
//return user;
}
catch (Exception)
{
throw;
}
}
public async Task ResetPassword(string emailAddress, string newPassword)
{
var filter = Builders<User>.Filter.Eq(u => u.EmailAddress, emailAddress);
var update = Builders<User>.Update.Set(u => u.Password, newPassword);
await dbContext.Users.UpdateOneAsync(filter, update);
}
public async Task UpdateAdminActivationStatus(string emailAddress, bool isActivated)
{
var filter = Builders<Admin>.Filter.Eq(u => u.EmailAddress, emailAddress);
var update = Builders<Admin>.Update.Set(u => u.isActivated, isActivated);
await dbContext.Admins.UpdateOneAsync(filter, update);
}
public async Task UpdateUserActivationStatus(string emailAddress, bool isActivated)
{
var filter = Builders<User>.Filter.Eq(u => u.EmailAddress, emailAddress);
var update = Builders<User>.Update.Set(u => u.isActivated, isActivated);
await dbContext.Users.UpdateOneAsync(filter, update);
}
}
}
Next is the NewResult class , that's been returned in the service layer
namespace Lacariz.Furniture.Domain.Common.Generics
{
public class NewResult
{
public string ResponseCode { get; set; }
public string ResponseMsg { get; set; }
}
public class NewLoginResult
{
public string ResponseCode { get; set; }
public string ResponseMsg { get; set; }
public string Token { get; set; }
}
public class NewLoginResult<T> : NewLoginResult
{
public T ResponseDetails { get; set; }
public string Token { get; set; } // JWT token property
public static NewLoginResult<T> Success(T instance, string token, string message = "successful")
{
return new NewLoginResult<T>
{
ResponseCode = "00",
ResponseDetails = instance,
ResponseMsg = message,
Token = token // Set the JWT token
};
}
public static NewLoginResult<T> Failed(T instance, string message = "BadRequest")
{
return new NewLoginResult<T>
{
ResponseCode = "99",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewLoginResult<T> Error(T instance, string message = "An error occured while processing your request")
{
return new NewLoginResult<T>
{
ResponseCode = "55",
ResponseDetails = instance,
ResponseMsg = message,
};
}
}
public class NewResult<T> : NewResult
{
public T ResponseDetails { get; set; }
public static NewResult<T> Success(T instance, string message = "successful")
{
return new NewResult<T>
{
ResponseCode = "00",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> Failed(T instance, string message = "BadRequest")
{
return new NewResult<T>
{
ResponseCode = "99",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> Unauthorized(T instance, string message = "Unauthorized")
{
return new NewResult<T>
{
ResponseCode = "41",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> RestrictedAccess(T instance, string message = "Unauthorized access")
{
return new()
{
ResponseCode = "40",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> InternalServerError(T instance, string message = "Internal Server Error")
{
return new()
{
ResponseCode = "55",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> SessionExpired(T instance, string message = "Session Expired")
{
return new()
{
ResponseCode = "41",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> Error(T instance, string message = "An error occured while processing your request")
{
return new NewResult<T>
{
ResponseCode = "55",
ResponseDetails = instance,
ResponseMsg = message,
};
}
public static NewResult<T> Duplicate(T instance, string message = "Duplicate request")
{
return new NewResult<T>
{
ResponseCode = "77",
ResponseDetails = instance,
ResponseMsg = message,
};
}
}
}
Now the user service layer , comprising of both the Interface and Implementation
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Domain.Entities;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IUserService
{
Task<NewResult<User>> RegisterUser(User user);
Task<NewResult<User>> ActivateAccount(string emailAddress, string activationCode);
Task<NewLoginResult<User>> UserLogin(LoginRequest loginRequest);
Task<NewLoginResult<Admin>> AdminLogin(AdminLoginRequest loginRequest);
Task<NewResult<string>> ResetPassword(string emailAddress, string verificationCode, string newPassword);
Task<NewResult<string>> InitiatePasswordReset(string emailAddress);
Task<NewResult<Admin>> RegisterAdmin(Admin admin);
Task<NewResult<Admin>> ActivateAdminAccount(string emailAddress, string activationCode);
Task<NewResult<string>> ResendVerificationCode(string emailAddress);
}
}
User Service Implementation
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class UserService : IUserService
{
private readonly IUserRepository userRepository;
private readonly IEmailService emailService;
private const string CacheKeyPrefix = "VerificationCode_";
private readonly IMemoryCache cache;
private readonly string jwtSecret = "hjejehukkehheukndhuywuiuwjbncduhbwiubdvuwyveyduwivuyegvryefrebuhjwbfjweuhbwllo"; // Change this to a secure secret key
private readonly double jwtExpirationMinutes = 60; // Token expiration time in minutes
private readonly ILogger logger;
public UserService(IUserRepository userRepository, IEmailService emailService, IMemoryCache cache, ILogger logger)
{
this.userRepository = userRepository;
this.emailService = emailService;
this.cache = cache;
this.logger = logger;
}
// private const string SessionKeyPrefix = "VerificationCode_";
public async Task<NewResult<User>> RegisterUser(User user)
{
// Hash the password
user.Password = HashPassword(user.Password);
NewResult<User> result = new NewResult<User>();
try
{
var userExists = await userRepository.GetUserByEmail(user.EmailAddress);
if (userExists != null)
{
return NewResult<User>.Duplicate(null, "Email address unavailable");
}
var response = await userRepository.RegisterUser(user);
if (response == null)
{
result = NewResult<User>.Failed(null, "Failed");
}
var newResponse = new User()
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
EmailAddress = user.EmailAddress,
Password = user.Password,
PhoneNumber = user.PhoneNumber,
Address = user.Address,
isActivated = false,
Role = user.Role
};
// Generate activation code
string verificationCode = GenerateVerificationCode();
// Store the verification code temporarily (example: in session or cache)
StoreVerificationCodeTemporarily(user.EmailAddress, verificationCode);
//// Construct activation link
//// string activationLink = $"https://yourdomain.com/activate?token={verificationCode}";
// Send activation email with the activation link
await emailService.SendActivationEmail(user.EmailAddress, verificationCode);
result = NewResult<User>.Success(newResponse, "User registration successful");
return result;
}
catch (Exception)
{
throw;
}
}
public async Task<NewResult<User>> ActivateAccount(string emailAddress, string activationCode)
{
NewResult<User> result = new NewResult<User>();
try
{
// Retrieve the stored verification code for the given email address
string storedVerificationCode = RetrieveVerificationCode(emailAddress);
// Check if the verification code is found in the cache
if (string.IsNullOrEmpty(storedVerificationCode))
{
// If the verification code is not found, it means it has expired or does not exist
return NewResult<User>.Failed(null, "Verification code not found or expired.");
}
// Check if the verification code matches the one provided by the user
if (storedVerificationCode != activationCode)
{
// If the verification code does not match, return a failure result
return NewResult<User>.Failed(null, "Invalid verification code.");
}
// Proceed with account activation
// Retrieve the user by email address
var user = await userRepository.GetUserByEmail(emailAddress);
if (user is not null)
{
// Activate the user account (example: set IsActive flag to true)
user.isActivated = true;
// Update the user in the database
await userRepository.UpdateUserActivationStatus(emailAddress, user.isActivated);
// Optionally, you may want to remove the verification code from storage
RemoveVerificationCodeFromCache(emailAddress);
// Return a success result
return NewResult<User>.Success(null, "Account activated successfully.");
}
else
{
// If the user is not found, return a failure result
return NewResult<User>.Failed(null, "User not found.");
}
}
catch (Exception ex)
{
// If an exception occurs during the activation process, return a failure result
return NewResult<User>.Error(null, $"Error activating account: {ex.Message}");
}
}
public async Task<NewLoginResult<User>> UserLogin(LoginRequest loginRequest)
{
NewLoginResult<User> result = new NewLoginResult<User>();
try
{
var user = await userRepository.GetUserByEmail(loginRequest.EmailAddress);
if (user == null || !VerifyPassword(loginRequest.Password, user.Password))
{
//throw new Exception("Invalid email or password.");
return NewLoginResult<User>.Failed(null, "Invalid email or password");
}
if (!user.isActivated)
{
return NewLoginResult<User>.Failed(null, "Account is not activated");
}
// Generate JWT token
var token = GenerateJwtToken(user);
result = NewLoginResult<User>.Success(user, token, "Login successful");
return result;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewLoginResult<User>.Error(null, "An error occured while trying to login");
}
}
private string GenerateVerificationCode()
{
// Generate a random 6-digit verification code
Random random = new Random();
int verificationCode = random.Next(100000, 999999);
return verificationCode.ToString();
}
public void StoreVerificationCodeTemporarily(string emailAddress, string verificationCode)
{
// Generate a unique cache key for the verification code
string cacheKey = $"{CacheKeyPrefix}{emailAddress}";
// Store the verification code in the MemoryCache with a sliding expiration time
cache.Set(cacheKey, verificationCode, TimeSpan.FromMinutes(10));
}
public string RetrieveVerificationCode(string emailAddress)
{
// Generate the cache key for the given email address
string cacheKey = $"{CacheKeyPrefix}{emailAddress}";
// Retrieve the verification code from the MemoryCache
return cache.Get<string>(cacheKey);
}
private string HashPassword(string password)
{
// Hash the password using BCrypt
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(password);
return hashedPassword;
}
private bool VerifyPassword(string enteredPassword, string hashedPassword)
{
// Verify the entered password against the hashed password using BCrypt
return BCrypt.Net.BCrypt.Verify(enteredPassword, hashedPassword);
}
private string GenerateJwtToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(jwtSecret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, user.Id),
// You can add more claims here as needed
}),
Expires = DateTime.UtcNow.AddMinutes(jwtExpirationMinutes),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private string GenerateAdminJwtToken(Admin admin)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(jwtSecret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, admin.AdminId),
// You can add more claims here as needed
}),
Expires = DateTime.UtcNow.AddMinutes(jwtExpirationMinutes),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public async Task<NewResult<string>> ResetPassword(string emailAddress, string verificationCode, string newPassword)
{
NewResult<string> result = new NewResult<string>();
try
{
// Step 1: Check if the email exists
if (await userRepository.EmailExists(emailAddress))
{
// Step 2: Generate and send verification code to the email
string generatedVerificationCode = GenerateVerificationCode();
await emailService.SendActivationEmail(emailAddress, generatedVerificationCode);
// Step 3: Verify verification code
if (await VerifyVerificationCode(emailAddress, verificationCode))
{
// Step 4: Proceed and reset password
await userRepository.ResetPassword(emailAddress, newPassword);
return NewResult<string>.Success(null, "Password reset successfully");
}
else
{
return NewResult<string>.Failed(null, "Invalid verification code");
}
}
else
{
return NewResult<string>.Failed(null, "Email address does not exist");
}
}
catch (Exception)
{
throw;
}
}
private async Task<bool> VerifyVerificationCode(string emailAddress, string verificationCode)
{
// Retrieve the stored verification code from the cache
string storedVerificationCode = RetrieveVerificationCode(emailAddress);
// Check if the verification code matches
return storedVerificationCode == verificationCode;
}
private void RemoveVerificationCodeFromCache(string emailAddress)
{
// Generate the cache key for the given email address
string cacheKey = $"{CacheKeyPrefix}{emailAddress}";
// Remove the verification code from the cache
cache.Remove(cacheKey);
}
public async Task<NewResult<string>> InitiatePasswordReset(string emailAddress)
{
try
{
// Step 1: Check if the email exists
if (await userRepository.EmailExists(emailAddress))
{
// Step 2: Generate a verification code
string generatedVerificationCode = GenerateVerificationCode();
// Step 3: Send the verification code to the user's email
await emailService.SendActivationEmail(emailAddress, generatedVerificationCode);
// Step 4: Return success response with the generated verification code
return NewResult<string>.Success(generatedVerificationCode, "Verification code sent to your email.");
}
else
{
// Email address does not exist, return failure response
return NewResult<string>.Failed(null, "Email address does not exist");
}
}
catch (Exception)
{
throw;
}
}
public async Task<NewResult<Admin>> RegisterAdmin(Admin admin)
{
try
{
// Hash the password
admin.Password = HashPassword(admin.Password);
NewResult<Admin> result = new NewResult<Admin>();
var AdminExists = await userRepository.GetAdminByEmail(admin.EmailAddress);
if (AdminExists != null)
{
return NewResult<Admin>.Duplicate(null, "Email address unavailable");
}
// Download the image data as byte array before creating the Admin object
byte[] profilePictureData = await DownloadImageAsByteArray(admin.ProfilePictureUrl);
// Instantiate the Admin object with the profile picture byte array
Admin aadmin = new Admin
{
AdminId = admin.AdminId,
FirstName = admin.FirstName,
LastName = admin.LastName,
EmailAddress = admin.EmailAddress,
Password = admin.Password,
ProfilePictureUrl = admin.ProfilePictureUrl,
AdminLoginId = "A86478927",
isActivated = admin.isActivated,
Role = admin.Role
};
var response = await userRepository.RegisterAdmin(aadmin);
if (response == null)
{
result = NewResult<Admin>.Failed(null, "Failed");
}
var newResponse = new Admin()
{
AdminId = admin.AdminId,
FirstName = admin.FirstName,
LastName = admin.LastName,
EmailAddress = admin.EmailAddress,
Password = admin.Password,
ProfilePictureUrl = admin.ProfilePictureUrl,
AdminLoginId = "A86478927",
//PhoneNumber = admin.,
// Address = admin.,
isActivated = false,
Role = admin.Role
};
// Generate activation code
string verificationCode = GenerateVerificationCode();
// Store the verification code temporarily (example: in session or cache)
StoreVerificationCodeTemporarily(admin.EmailAddress, verificationCode);
//// Construct activation link
//// string activationLink = $"https://yourdomain.com/activate?token={verificationCode}";
// Send activation email with the activation link
await emailService.SendActivationEmail(admin.EmailAddress, verificationCode);
result = NewResult<Admin>.Success(newResponse, "Admin registration successful");
return result;
}
catch (Exception)
{
throw;
}
}
public async Task<NewLoginResult<Admin>> AdminLogin(AdminLoginRequest loginRequest)
{
NewLoginResult<Admin> result = new NewLoginResult<Admin>();
try
{
var admin = await userRepository.GetAdminByLoginId(loginRequest.AdminLoginId);
if (admin == null || !VerifyPassword(loginRequest.Password, admin.Password))
{
//throw new Exception("Invalid email or password.");
return NewLoginResult<Admin>.Failed(null, "Invalid login credientials");
}
if (!admin.isActivated)
{
return NewLoginResult<Admin>.Failed(null, "Account is not activated");
}
// Generate JWT token
var token = GenerateAdminJwtToken(admin);
result = NewLoginResult<Admin>.Success(admin, token, "Login successful");
return result;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewLoginResult<Admin>.Error(null, "An error occured while trying to login");
}
}
public async Task<byte[]> DownloadImageAsByteArray(string imageUrl)
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(imageUrl);
if (response.IsSuccessStatusCode)
{
using (var stream = await response.Content.ReadAsStreamAsync())
{
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
}
else
{
// Handle error response
throw new Exception($"Failed to download image from URL: {imageUrl}.");
}
}
}
public async Task<NewResult<Admin>> ActivateAdminAccount(string emailAddress, string activationCode)
{
// NewResult<User> result = new NewResult<User>();
try
{
// Retrieve the stored verification code for the given email address
string storedVerificationCode = RetrieveVerificationCode(emailAddress);
// Check if the verification code is found in the cache
if (string.IsNullOrEmpty(storedVerificationCode))
{
// If the verification code is not found, it means it has expired or does not exist
return NewResult<Admin>.Failed(null, "Verification code not found or expired.");
}
// Check if the verification code matches the one provided by the user
if (storedVerificationCode != activationCode)
{
// If the verification code does not match, return a failure result
return NewResult<Admin>.Failed(null, "Invalid verification code.");
}
// Proceed with account activation
// Retrieve the admin by email address
var admin = await userRepository.GetAdminByEmail(emailAddress);
if (admin is not null)
{
// Activate the user account (example: set IsActive flag to true)
admin.isActivated = true;
// Update the user in the database
await userRepository.UpdateAdminActivationStatus(emailAddress, admin.isActivated);
// Optionally, you may want to remove the verification code from storage
RemoveVerificationCodeFromCache(emailAddress);
// Return a success result
return NewResult<Admin>.Success(null, "Account activated successfully.");
}
else
{
// If the user is not found, return a failure result
return NewResult<Admin>.Failed(null, "Admin not found.");
}
}
catch (Exception ex)
{
// If an exception occurs during the activation process, return a failure result
return NewResult<Admin>.Failed(null, $"Error activating account: {ex.Message}");
}
}
public async Task<NewResult<string>> ResendVerificationCode(string emailAddress)
{
try
{
// Generate a new verification code
string newVerificationCode = GenerateVerificationCode();
// Store the new verification code temporarily
StoreVerificationCodeTemporarily(emailAddress, newVerificationCode);
// Send the new verification code to the user's email address
await emailService.SendActivationEmail(emailAddress, newVerificationCode);
return NewResult<string>.Success(null, "Verification code resent successfully.");
}
catch (Exception ex)
{
// Handle any errors that occur during the resend process
return NewResult<string>.Failed(null, $"Failed to resend verification code: {ex.Message}");
}
}
}
}
Authentication Controller
Authentication controller extends Base Controller.
Base Controller
using Lacariz.Furniture.Domain.Common;
namespace Lacariz.Furniture.API.Controllers.v1;
[Route("lacariz-furniture-service")]
//[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
public class BaseController : ControllerBase
{
public BaseController()
{
}
internal Error PopulateError(int code, string message, string type)
{
return new Error()
{
Code = code,
Message = message,
Type = type
};
}
}
Authentication Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class AuthenticationController : BaseController
{
private readonly IUserService userService;
public AuthenticationController(IUserService userService)
{
this.userService = userService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/user/register")]
[ApiVersion("1.0")]
public async Task<IActionResult> RegisterUser([FromBody] User request)
{
var response = await userService.RegisterUser(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/admin/register")]
[ApiVersion("1.0")]
public async Task<IActionResult> RegisterAdmin([FromBody] Admin request)
{
var response = await userService.RegisterAdmin(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/user/login")]
[ApiVersion("1.0")]
public async Task<IActionResult> UserLogin([FromBody] LoginRequest request)
{
var response = await userService.UserLogin(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/admin/login")]
[ApiVersion("1.0")]
public async Task<IActionResult> AdminLogin([FromBody] AdminLoginRequest request)
{
var response = await userService.AdminLogin(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/user/reset-password")]
[ApiVersion("1.0")]
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordRequest request)
{
var response = await userService.ResetPassword(request.EmailAddress, request.VerificationCode, request.NewPassword);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/user/activate-user-account")]
[ApiVersion("1.0")]
public async Task<IActionResult> ActivateUserAccount([FromBody] ActivateAccountRequest request)
{
var response = await userService.ActivateAccount(request.EmailAddress, request.ActivationCode);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/admin/activate-admin-account")]
[ApiVersion("1.0")]
public async Task<IActionResult> ActivateAdminAccount([FromBody] ActivateAccountRequest request)
{
var response = await userService.ActivateAdminAccount(request.EmailAddress, request.ActivationCode);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/resend-verification-code")]
[ApiVersion("1.0")]
public async Task<IActionResult> ResendVerificationCode(string EmailAddress)
{
var response = await userService.ResendVerificationCode(EmailAddress);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Customer Support Service for Lacariz Furniture E-commerce Application
Next, we will implement the customer support service, which involves setting up the necessary repository, service, and controller layers.
CustomerSupport Repository
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface ICustomerSupportRepository
{
Task AddInquiryAsync(CustomerInquiry inquiry);
Task<IEnumerable<CustomerInquiry>> GetInquiriesByUserIdAsync(string userId);
Task<CustomerInquiry> GetInquiryByIdAsync(string inquiryId);
Task<bool> UpdateInquiryAsync(CustomerInquiry inquiry);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class CustomerSupportRepository : ICustomerSupportRepository
{
private readonly IMongoDBLogContext dbContext;
public CustomerSupportRepository(IMongoDBLogContext dbContext)
{
dbContext = dbContext;
}
public async Task AddInquiryAsync(CustomerInquiry inquiry)
{
await dbContext.CustomerInquiries.InsertOneAsync(inquiry);
}
public async Task<IEnumerable<CustomerInquiry>> GetInquiriesByUserIdAsync(string userId)
{
var filter = Builders<CustomerInquiry>.Filter.Eq(i => i.UserId, userId);
return await dbContext.CustomerInquiries.Find(filter).ToListAsync();
}
public async Task<CustomerInquiry> GetInquiryByIdAsync(string inquiryId)
{
var filter = Builders<CustomerInquiry>.Filter.Eq(i => i.Id, inquiryId);
return await dbContext.CustomerInquiries.Find(filter).FirstOrDefaultAsync();
}
public async Task<bool> UpdateInquiryAsync(CustomerInquiry inquiry)
{
var filter = Builders<CustomerInquiry>.Filter.Eq(i => i.Id, inquiry.Id);
var result = await dbContext.CustomerInquiries.ReplaceOneAsync(filter, inquiry);
return result.ModifiedCount > 0;
}
}
}
CustomerSupport Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface ICustomerSupportService
{
Task<NewResult<string>> SubmitInquiryAsync(string userId, string subject, string message);
Task<NewResult<IEnumerable<CustomerInquiry>>> GetUserInquiriesAsync(string userId);
Task<NewResult<CustomerInquiry>> GetInquiryByIdAsync(string inquiryId);
Task<NewResult<string>> ResolveInquiryAsync(string inquiryId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class CustomerSupportService : ICustomerSupportService
{
private readonly ICustomerSupportRepository customerSupportRepository;
public CustomerSupportService(ICustomerSupportRepository customerSupportRepository)
{
this.customerSupportRepository = customerSupportRepository;
}
public async Task<NewResult<CustomerInquiry>> GetInquiryByIdAsync(string inquiryId)
{
try
{
if (string.IsNullOrEmpty(inquiryId))
throw new ArgumentNullException(nameof(inquiryId), "Inquiry ID cannot be null or empty.");
var inquiry = await customerSupportRepository.GetInquiryByIdAsync(inquiryId);
return NewResult<CustomerInquiry>.Success(inquiry, "Inquiry retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<CustomerInquiry>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<IEnumerable<CustomerInquiry>>> GetUserInquiriesAsync(string userId)
{
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId), "User ID cannot be null or empty.");
var inquiries = await customerSupportRepository.GetInquiriesByUserIdAsync(userId);
return NewResult<IEnumerable<CustomerInquiry>>.Success(inquiries, "Inquiries retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<IEnumerable<CustomerInquiry>>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<string>> ResolveInquiryAsync(string inquiryId)
{
try
{
if (string.IsNullOrEmpty(inquiryId))
throw new ArgumentNullException(nameof(inquiryId), "Inquiry ID cannot be null or empty.");
var inquiry = await customerSupportRepository.GetInquiryByIdAsync(inquiryId);
if (inquiry == null)
return NewResult<string>.Failed(null, "Inquiry not found.");
inquiry.IsResolved = true;
var updated = await customerSupportRepository.UpdateInquiryAsync(inquiry);
return updated ? NewResult<string>.Success(inquiryId, "Inquiry resolved successfully.")
: NewResult<string>.Failed(inquiryId, "Failed to resolve inquiry.");
}
catch (Exception ex)
{
return NewResult<string>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<string>> SubmitInquiryAsync(string userId, string subject, string message)
{
try
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(subject) || string.IsNullOrEmpty(message))
throw new ArgumentNullException("Invalid inquiry data");
var inquiry = new CustomerInquiry
{
Id = Guid.NewGuid().ToString(),
UserId = userId,
Subject = subject,
Message = message,
CreatedAt = DateTime.UtcNow,
IsResolved = false
};
await customerSupportRepository.AddInquiryAsync(inquiry);
return NewResult<string>.Success(inquiry.Id, "Inquiry submitted successfully.");
}
catch (Exception ex)
{
return NewResult<string>.Failed(null, $"Error occurred: {ex.Message}");
}
}
}
}
Customer Support Controller
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class CustomerSupportController : BaseController
{
private readonly ICustomerSupportService customerSupportService;
public CustomerSupportController(ICustomerSupportService customerSupportService)
{
this.customerSupportService = customerSupportService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/support/submit-inquiry")]
[ApiVersion("1.0")]
public async Task<IActionResult> SubmitInquiry([FromBody] CustomerInquiry request)
{
var response = await customerSupportService.SubmitInquiryAsync(request.UserId, request.Subject, request.Message);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/support/admin/get-user-inquiries")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetUserInquiries(string userId)
{
var response = await customerSupportService.GetUserInquiriesAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/support/admin/get-inquiry-by-id")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetInquiryById(string inquiryId)
{
var response = await customerSupportService.GetInquiryByIdAsync(inquiryId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/support/admin/resolve-inquiry")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> ResolveInquiry(string inquiryId)
{
var response = await customerSupportService.ResolveInquiryAsync(inquiryId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Inventory Service for Lacariz Furniture E-commerce Application
Next, we will implement the inventory service, which involves setting up the necessary repository, service, and controller layers.
Inventory Repository
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IInventoryRepository
{
Task<bool> UpdateStockLevelAsync(string furnitureItemId, int newStockLevel);
Task<IEnumerable<FurnitureItem>> GetCurrentStockLevelsAsync();
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class InventoryRepository : IInventoryRepository
{
private readonly IMongoDBLogContext DbContext;
public InventoryRepository(IMongoDBLogContext dbContext)
{
DbContext = dbContext;
}
public async Task<bool> UpdateStockLevelAsync(string furnitureItemId, int newStockLevel)
{
var filter = Builders<FurnitureItem>.Filter.Eq(f => f.Id, furnitureItemId);
var update = Builders<FurnitureItem>.Update.Set(f => f.StockQuantity, newStockLevel);
var result = await DbContext.FurnitureItems.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}
public async Task<IEnumerable<FurnitureItem>> GetCurrentStockLevelsAsync()
{
return await DbContext.FurnitureItems.Find(FilterDefinition<FurnitureItem>.Empty).ToListAsync();
}
}
}
Inventory Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IInventoryService
{
Task<NewResult<bool>> UpdateStockLevelAsync(string furnitureItemId, int newStockLevel);
Task<NewResult<IEnumerable<FurnitureItem>>> GetCurrentStockLevelsAsync();
}
}
using Lacariz.Furniture.Data.Repositories.Implementations;
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class InventoryService : IInventoryService
{
private readonly IInventoryRepository inventoryRepository;
public InventoryService(IInventoryRepository inventoryRepository)
{
this.inventoryRepository = inventoryRepository;
}
public async Task<NewResult<IEnumerable<FurnitureItem>>> GetCurrentStockLevelsAsync()
{
try
{
var stockLevels = await inventoryRepository.GetCurrentStockLevelsAsync();
if (stockLevels != null && stockLevels.Any())
{
return NewResult<IEnumerable<FurnitureItem>>.Success(stockLevels, "Current stock levels retrieved successfully.");
}
else
{
return NewResult<IEnumerable<FurnitureItem>>.Failed(null, "No stock levels found.");
}
}
catch (Exception ex)
{
return NewResult<IEnumerable<FurnitureItem>>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<bool>> UpdateStockLevelAsync(string furnitureItemId, int newStockLevel)
{
try
{
if (string.IsNullOrEmpty(furnitureItemId))
throw new ArgumentNullException(nameof(furnitureItemId), "Furniture item ID cannot be null or empty.");
var result = await inventoryRepository.UpdateStockLevelAsync(furnitureItemId, newStockLevel);
if (result)
{
return NewResult<bool>.Success(true, "Stock level updated successfully.");
}
else
{
return NewResult<bool>.Failed(false, "Failed to update stock level.");
}
}
catch (Exception ex)
{
return NewResult<bool>.Failed(false, $"Error occurred: {ex.Message}");
}
}
}
}
Inventory Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class InventoryController : BaseController
{
private readonly IInventoryService inventoryService;
public InventoryController(IInventoryService inventoryService)
{
this.inventoryService = inventoryService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/update-stock-level")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> UpdateStockLevel(StockLevelRequest request)
{
var response = await inventoryService.UpdateStockLevelAsync(request.FurnitureItemId, request.NewStockLevel);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/get-stock-level")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetStockLevels()
{
var response = await inventoryService.GetCurrentStockLevelsAsync();
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Order Service for Lacariz Furniture E-commerce Application
Next, we will implement the order service, which involves setting up the necessary repository, service, and controller layers.
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IOrderRepository
{
Task<Order> CreateOrderAsync(Order order);
Task<Order> GetOrderByIdAsync(string orderId);
Task<IEnumerable<Order>> GetOrdersByUserIdAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class OrderRepository : IOrderRepository
{
private readonly IMongoDBLogContext DbContext;
public OrderRepository(IMongoDBLogContext dblogContext)
{
DbContext = DbContext;
}
public async Task<Order> CreateOrderAsync(Order order)
{
await DbContext.Orders.InsertOneAsync(order);
return order;
}
public async Task<Order> GetOrderByIdAsync(string orderId)
{
return await DbContext.Orders.Find(o => o.Id == orderId).FirstOrDefaultAsync();
}
public async Task<IEnumerable<Order>> GetOrdersByUserIdAsync(string userId)
{
return await DbContext.Orders.Find(o => o.UserId == userId).ToListAsync();
}
}
}
Order Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IOrderService
{
Task<NewResult<Order>> CreateOrderAsync(string userId, List<OrderItem> items);
Task<NewResult<Order>> GetOrderByIdAsync(string orderId);
Task<NewResult<IEnumerable<Order>>> GetOrdersByUserIdAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using Lacariz.Furniture.Service.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class OrderService : IOrderService
{
private readonly IOrderRepository orderRepository;
public OrderService(IOrderRepository orderRepository)
{
this.orderRepository = orderRepository;
}
public async Task<NewResult<Order>> CreateOrderAsync(string userId, List<OrderItem> items)
{
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId));
if (items == null || items.Count == 0)
throw new ArgumentNullException(nameof(items));
var order = new Order
{
UserId = userId,
Items = items,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending,
TotalAmount = items.Sum(i => i.Quantity * i.Price)
};
var createdOrder = await orderRepository.CreateOrderAsync(order);
if (createdOrder != null)
{
return NewResult<Order>.Success(createdOrder, "Order created successfully.");
}
return NewResult<Order>.Failed(null, "Unable to create order");
}
catch (Exception ex)
{
return NewResult<Order>.Error(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<Order>> GetOrderByIdAsync(string orderId)
{
try
{
var order = await orderRepository.GetOrderByIdAsync(orderId);
if (order == null)
{
return NewResult<Order>.Failed(null, "Order not found.");
}
return NewResult<Order>.Success(order, "Order retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<Order>.Error(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<IEnumerable<Order>>> GetOrdersByUserIdAsync(string userId)
{
try
{
var orders = await orderRepository.GetOrdersByUserIdAsync(userId);
if (orders == null || !orders.Any())
{
return NewResult<IEnumerable<Order>>.Failed(null, "No orders found.");
}
return NewResult<IEnumerable<Order>>.Success(orders, "Orders retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<IEnumerable<Order>>.Error(null, $"Error occurred: {ex.Message}");
}
}
}
}
Order Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class OrderController : BaseController
{
private readonly IOrderService orderService;
public OrderController(IOrderService orderService)
{
this.orderService = orderService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/create-order")]
[ApiVersion("1.0")]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var response = await orderService.CreateOrderAsync(request.UserId, request.items);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-order-by-id")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetOrderById(string orderId)
{
var response = await orderService.GetOrderByIdAsync(orderId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-order-by-user-id")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetOrdersByUserId(string userId)
{
var response = await orderService.GetOrdersByUserIdAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Pre-Order Service for Lacariz Furniture E-commerce Application
Next, we will implement the pre-order service, which involves setting up the necessary repository, service, and controller layers.
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IPreOrderRepository
{
Task<PreOrder> CreatePreOrderAsync(PreOrder preOrder);
Task<PreOrder> GetPreOrderByIdAsync(string preorderId);
Task<IEnumerable<PreOrder>> GetPreOrdersByUserIdAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class PreOrderRepository : IPreOrderRepository
{
private readonly IMongoDBLogContext DbContext;
public PreOrderRepository(IMongoDBLogContext dbContext)
{
DbContext = dbContext;
}
public async Task<PreOrder> CreatePreOrderAsync(PreOrder preOrder)
{
await DbContext.PreOrders.InsertOneAsync(preOrder);
return preOrder;
}
public async Task<PreOrder> GetPreOrderByIdAsync(string preorderId)
{
return await DbContext.PreOrders.Find(po => po.Id == preorderId).FirstOrDefaultAsync();
}
public async Task<IEnumerable<PreOrder>> GetPreOrdersByUserIdAsync(string userId)
{
return await DbContext.PreOrders.Find(po => po.UserId == userId).ToListAsync();
}
}
}
Pre-Order Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IPreOrderService
{
Task<NewResult<PreOrder>> CreatePreOrderAsync(string userId, string furnitureItemId, int quantity);
Task<NewResult<PreOrder>> GetPreOrderByIdAsync(string preorderId);
Task<NewResult<IEnumerable<PreOrder>>> GetPreOrdersByUserIdAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using Lacariz.Furniture.Service.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class PreOrderService : IPreOrderService
{
private readonly IPreOrderRepository preOrderRepository;
public PreOrderService(IPreOrderRepository preOrderRepository)
{
this.preOrderRepository = preOrderRepository;
}
public async Task<NewResult<PreOrder>> CreatePreOrderAsync(string userId, string furnitureItemId, int quantity)
{
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId));
if (string.IsNullOrEmpty(furnitureItemId))
throw new ArgumentNullException(nameof(furnitureItemId));
var preOrder = new PreOrder
{
UserId = userId,
FurnitureItemId = furnitureItemId,
PreOrderDate = DateTime.UtcNow,
Status = PreOrderStatus.Pending,
Quantity = quantity
};
var createdPreOrder = await preOrderRepository.CreatePreOrderAsync(preOrder);
return NewResult<PreOrder>.Success(createdPreOrder, "Pre-order created successfully.");
}
catch (Exception ex)
{
return NewResult<PreOrder>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<PreOrder>> GetPreOrderByIdAsync(string preorderId)
{
try
{
var preOrder = await preOrderRepository.GetPreOrderByIdAsync(preorderId);
if (preOrder == null)
{
return NewResult<PreOrder>.Failed(null, "Pre-order not found.");
}
return NewResult<PreOrder>.Success(preOrder, "Pre-order retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<PreOrder>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<IEnumerable<PreOrder>>> GetPreOrdersByUserIdAsync(string userId)
{
try
{
var preOrders = await preOrderRepository.GetPreOrdersByUserIdAsync(userId);
if (preOrders == null || !preOrders.Any())
{
return NewResult<IEnumerable<PreOrder>>.Failed(null, "No pre-orders found.");
}
return NewResult<IEnumerable<PreOrder>>.Success(preOrders, "Pre-orders retrieved successfully.");
}
catch (Exception ex)
{
return NewResult<IEnumerable<PreOrder>>.Failed(null, $"Error occurred: {ex.Message}");
}
}
}
}
Pre-Order Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class PreOrderController : BaseController
{
private readonly IPreOrderService preOrderService;
public PreOrderController(IPreOrderService preOrderService)
{
this.preOrderService = preOrderService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/create-pre-order")]
[ApiVersion("1.0")]
public async Task<IActionResult> CreatePreOrder(CreatePreOrderRequest request)
{
var response = await preOrderService.CreatePreOrderAsync(request.UserId, request.FurnitureItemId, request.Quantity);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-pre-order-by-id")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetPreOrderById(string orderId)
{
var response = await preOrderService.GetPreOrderByIdAsync(orderId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-pre-order-by-user-id")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetPreOrdersByUserId(string userId)
{
var response = await preOrderService.GetPreOrdersByUserIdAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Payment Service for Lacariz Furniture E-commerce Application
To implement the payment service for the Lacariz Furniture e-commerce application, we set up the necessary repository, service, and controller layers. The payment service handles interactions with Paystack and Flutterwave for processing payments. We created a Payment entity to represent payment data and defined a repository interface and implementation for handling payment records. A service layer was developed to encapsulate the business logic for processing payments, including integration with Paystack and Flutterwave APIs. Finally, a PaymentController was added to expose endpoints for initiating and managing payments.
PATSTACK SERVICE
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Verification;
namespace Lacariz.Furniture.Service.Services.External_Service.Interfaces
{
public interface IPaystackService
{
Task<PaystackPaymentInitiationResponse> InitiatePaymentAsync(PaystackPaymentInitiationRequest request, MyBankLog log);
Task<PaystackPaymentVerificationResponse> VerifyPaymentAsync(PaystackPaymentVerificationRequest request, MyBankLog log);
}
}
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Helpers.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Verification;
namespace Lacariz.Furniture.Service.Services.External_Service.Implementations
{
public class PaystackService : IPaystackService
{
// private readonly IConfiguration config;
private readonly IRestHelper restHelper;
private readonly ILogger logger;
public PaystackService(IRestHelper restHelper, ILogger logger)
{
// this.config = config;
this.restHelper = restHelper;
this.logger = logger;
}
public async Task<PaystackPaymentInitiationResponse> InitiatePaymentAsync(PaystackPaymentInitiationRequest request, MyBankLog log)
{
try
{
logger.Information("Initiate payment service");
string paystackApiUrl = Environment.GetEnvironmentVariable("InitiatePaymentApiUrl");
string paystackSecretKey = Environment.GetEnvironmentVariable("SecretKey");
var paystackRequest = new PaystackPaymentInitiationRequest
{
Amount = request.Amount,
Email = request.Email,
};
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {paystackSecretKey}" },
};
var initiationResponse = await restHelper.DoWebRequestAsync<PaystackPaymentInitiationResponse>(log,
paystackApiUrl,
paystackRequest,
"post",
headers);
return initiationResponse;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
log.AdditionalInformation = ex.Message;
return null;
}
}
public async Task<PaystackPaymentVerificationResponse> VerifyPaymentAsync(PaystackPaymentVerificationRequest request, MyBankLog log)
{
try
{
logger.Information("Verify payment service");
string paystackApiUrl = Environment.GetEnvironmentVariable("VerifyReferenceApiUrl") + $"/{request.Reference}";
string paystackSecretKey = Environment.GetEnvironmentVariable("SecretKey");
var paystackVerificationRequest = new PaystackPaymentVerificationRequest
{
Reference = request.Reference,
};
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {paystackSecretKey}" },
};
var verificationResponse = await restHelper.DoWebRequestAsync<PaystackPaymentVerificationResponse>(log,
paystackApiUrl,
null,
"get",
headers);
return verificationResponse;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
log.AdditionalInformation = ex.Message;
return null;
}
}
}
}
REST HELPER : For consuming external service using HTTP Client .
using Lacariz.Furniture.Domain.Entities;
namespace Lacariz.Furniture.Service.Helpers.Interfaces;
public interface IRestHelper
{
Task<T> DoWebRequestAsync<T>(MyBankLog log, string url, object request, string requestType, Dictionary<string, string> headers = null) where T : new();
}
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Helpers.Interfaces;
using RestSharp;
namespace Lacariz.Furniture.Service.Helpers.Implementations;
public class RestHelper : IRestHelper
{
private readonly ILogger _logger;
private readonly IConfiguration _config;
public RestHelper(ILogger logger, IConfiguration config)
{
_logger = logger;
_config = config;
}
public async Task<T> DoWebRequestAsync<T>(MyBankLog log, string url, object request, string requestType, Dictionary<string, string> headers = null) where T : new()
{
var SDS = JsonConvert.SerializeObject(request);
_logger.Information("URL: " + url + " " + JsonConvert.SerializeObject(request));
T result = new T();
Method method = requestType.ToLower() == "post" ? Method.Post : Method.Get;
var client = new RestClient(url);
var restRequest = new RestRequest(url, method);
if (method == Method.Post)
{
restRequest.RequestFormat = DataFormat.Json;
restRequest.AddJsonBody(request);
}
if (headers != null)
{
foreach (var item in headers)
{
restRequest.AddHeader(item.Key, item.Value);
}
}
try
{
RestResponse<T> response = await client.ExecuteAsync<T>(restRequest);
_logger.Information("URL: " + url + " " + response.Content);
if (!response.IsSuccessful)
{
log.AdditionalInformation = $"URL: {url} {response.Content}";
}
result = JsonConvert.DeserializeObject<T>(response.Content);
return result;
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
log.AdditionalInformation = $"URL: {url} {ex.Message}";
return result;
}
}
}
FlutterWave Service
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyPaymentResponse;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyResponse;
namespace Lacariz.Furniture.Service.Services.External_Service.Interfaces
{
public interface IFlutterwaveService
{
Task<FlutterwaveInitiateCardPaymentResponse> InitiatePaymentAsync(FlutterwaveInitiateCardPaymentRequest request, MyBankLog log);
Task<FlutterwaveValidateChargeResponse> ValidateChargeAsync(FlutterwaveValidateChargeRequest request, MyBankLog log);
Task<FlutterwaveVerifyCardPaymentResponse> VerifyPaymentAsync(FlutterwaveVerifyCardPaymentRequest request, MyBankLog log);
}
}
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Helpers.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyPaymentResponse;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyResponse;
using Authorization = Lacariz.Furniture.Service.Services.External_Service.Requests.Authorization;
namespace Lacariz.Furniture.Service.Services.External_Service.Implementations
{
public class FlutterwaveService : IFlutterwaveService
{
private readonly IRestHelper restHelper;
private readonly ILogger logger;
private readonly string encryptionKey;
private readonly IEncryptionHelper encryptionHelper;
public FlutterwaveService(IRestHelper restHelper, ILogger logger, IEncryptionHelper encryptionHelper)
{
this.restHelper = restHelper;
this.logger = logger;
this.encryptionHelper = encryptionHelper;
this.encryptionKey = Environment.GetEnvironmentVariable("FlutterwaveEncryptionKey");
}
public async Task<FlutterwaveInitiateCardPaymentResponse> InitiatePaymentAsync(FlutterwaveInitiateCardPaymentRequest request, MyBankLog log)
{
try
{
logger.Information("Initiate payment service");
string flutterwaveApiUrl = Environment.GetEnvironmentVariable("InitiateFlutterwaveApiUrl");
string flutterwaveSecretKey = Environment.GetEnvironmentVariable("FlutterwaveSecretKey");
var flutterwaveRequest = new FlutterwaveInitiateCardPaymentRequest
{
CardNumber = request.CardNumber,
CVV = request.CVV,
ExpiryMonth = request.ExpiryMonth,
ExpiryYear = request.ExpiryYear,
Currency = request.Currency,
Amount = request.Amount,
FullName = request.FullName,
Email = request.Email,
TransactionReference = request.TransactionReference,
RedirectUrl = "https://www.flutterwave.ng",
Authorization = new Authorization
{
Mode = request.Authorization.Mode,
City = request.Authorization.City,
Address = request.Authorization.Address,
State = request.Authorization.State,
Country = request.Authorization.Country,
Zipcode = request.Authorization.Zipcode
}
};
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {flutterwaveSecretKey}" },
};
// Serialize the request to JSON
string requestJson = JsonConvert.SerializeObject(flutterwaveRequest);
// Encrypt the JSON payload using 3DES
string encryptedPayload = encryptionHelper.Encrypt3DES(requestJson, encryptionKey);
var initiationResponse = await restHelper.DoWebRequestAsync<FlutterwaveInitiateCardPaymentResponse>(log,
flutterwaveApiUrl,
encryptedPayload,
"post",
headers);
return initiationResponse;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
log.AdditionalInformation = ex.Message;
return null;
}
}
public async Task<FlutterwaveValidateChargeResponse> ValidateChargeAsync(FlutterwaveValidateChargeRequest request, MyBankLog log)
{
try
{
logger.Information("Validate charge service");
string flutterwaveApiUrl = Environment.GetEnvironmentVariable("FlutterwaveValidateChargeApiUrl");
string flutterwaveSecretKey = Environment.GetEnvironmentVariable("FlutterwaveSecretKey");
var flutterwaveRequest = new FlutterwaveValidateChargeRequest
{
Otp = request.Otp,
Flw_ref = request.Flw_ref
};
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {flutterwaveSecretKey}" },
};
var verifyResponse = await restHelper.DoWebRequestAsync<FlutterwaveValidateChargeResponse>(log,
flutterwaveApiUrl,
flutterwaveRequest,
"post",
headers);
return verifyResponse;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
log.AdditionalInformation = ex.Message;
return null;
}
}
public async Task<FlutterwaveVerifyCardPaymentResponse> VerifyPaymentAsync(FlutterwaveVerifyCardPaymentRequest request, MyBankLog log)
{
try
{
logger.Information("Validate charge service");
string flutterwaveApiUrl = Environment.GetEnvironmentVariable("VerifyCardPaymentFlutterwaveApiUrl") + $"/{request.TransactionId}" + "/verify";
string flutterwaveSecretKey = Environment.GetEnvironmentVariable("FlutterwaveSecretKey");
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {flutterwaveSecretKey}" },
};
var verifyResponse = await restHelper.DoWebRequestAsync<FlutterwaveVerifyCardPaymentResponse>(log,
flutterwaveApiUrl,
null,
"get",
headers);
return verifyResponse;
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
log.AdditionalInformation = ex.Message;
return null;
}
}
}
}
Payment Service : This is the internal service that interfaces with both paystack and flutterwave
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyPaymentResponse;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyResponse;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Verification;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IPaymentService
{
Task<NewResult<PaystackPaymentInitiationResponse>> InitiatePayment(double amount, string email);
Task<NewResult<PaystackPaymentVerificationResponse>> VerifyPayment(string reference);
Task<NewResult<FlutterwaveInitiateCardPaymentResponse>> InitiateFlutterwaveCardPayment(FlutterwaveInitiateCardPaymentRequest request);
Task<NewResult<FlutterwaveValidateChargeResponse>> FlutterwaveValidateCharge(FlutterwaveValidateChargeRequest request);
Task<NewResult<FlutterwaveVerifyCardPaymentResponse>> FlutterwaveVerifyCardPayment(FlutterwaveVerifyCardPaymentRequest request);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Service.Services.External_Service.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Responses;
using Lacariz.Furniture.Service.Services.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Verification;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave;
using static MongoDB.Driver.WriteConcern;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyResponse;
using Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyPaymentResponse;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class PaymentService : IPaymentService
{
private readonly IPaystackService paystackService;
//private readonly ISampleRepository sampleRepository;
//private readonly IMyBankLogRepository myBankLogRepository;
private readonly ILogger logger;
private readonly IFlutterwaveService flutterwaveService;
public PaymentService(IPaystackService paystackService,
//ISampleRepository sampleRepository,
//IMyBankLogRepository myBankLogRepository,
ILogger logger,
IFlutterwaveService flutterwaveService)
{
this.paystackService = paystackService;
//this.sampleRepository = sampleRepository;
//this.myBankLogRepository = myBankLogRepository;
this.logger = logger;
this.flutterwaveService = flutterwaveService;
}
public async Task<NewResult<FlutterwaveValidateChargeResponse>> FlutterwaveValidateCharge(FlutterwaveValidateChargeRequest request)
{
MyBankLog dbLog = new MyBankLog();
try
{
if (string.IsNullOrWhiteSpace(request.Otp))
{
throw new ArgumentNullException(nameof(request.Otp), "OTP cannot be null or empty.");
}
if (string.IsNullOrWhiteSpace(request.Flw_ref))
{
throw new ArgumentNullException(nameof(request.Flw_ref), "FLW Reference cannot be null or empty.");
}
// Call the Flutterwave service to validate the charge
var validateChargeResponse = await flutterwaveService.ValidateChargeAsync(request, dbLog);
// Check the response from the service
if (validateChargeResponse != null && validateChargeResponse.Status.Equals("success", StringComparison.OrdinalIgnoreCase))
{
return NewResult<FlutterwaveValidateChargeResponse>.Success(validateChargeResponse, "Charge successfully validated.");
}
return NewResult<FlutterwaveValidateChargeResponse>.Failed(null, "Charge validation failed.");
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewResult<FlutterwaveValidateChargeResponse>.Error(null, $"Error while validating charge: {ex.Message}");
}
}
public async Task<NewResult<FlutterwaveVerifyCardPaymentResponse>> FlutterwaveVerifyCardPayment(FlutterwaveVerifyCardPaymentRequest request)
{
MyBankLog dbLog = new MyBankLog();
try
{
if (string.IsNullOrWhiteSpace(request.TransactionId))
{
throw new ArgumentNullException(nameof(request.TransactionId), "TransactionId cannot be null or empty.");
}
var verifyCardPayment = await flutterwaveService.VerifyPaymentAsync(request, dbLog);
// Check if the response is not null
if (verifyCardPayment == null)
{
return NewResult<FlutterwaveVerifyCardPaymentResponse>.Failed(null, "Verification failed: no response from payment service.");
}
// Check the status of the response
if (verifyCardPayment.Status != "success")
{
return NewResult<FlutterwaveVerifyCardPaymentResponse>.Failed(null, $"Verification failed: {verifyCardPayment.Message}");
}
// Check critical fields in the data
if (verifyCardPayment.Data == null ||
string.IsNullOrWhiteSpace(verifyCardPayment.Data.TxRef) ||
string.IsNullOrWhiteSpace(verifyCardPayment.Data.FlwRef))
{
return NewResult<FlutterwaveVerifyCardPaymentResponse>.Failed(null, "Verification failed: missing critical data in response.");
}
return NewResult<FlutterwaveVerifyCardPaymentResponse>.Success(verifyCardPayment, "Payment successfully verified.");
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewResult<FlutterwaveVerifyCardPaymentResponse>.Error(null, $"Error while verifying payment: {ex.Message}");
}
}
public async Task<NewResult<FlutterwaveInitiateCardPaymentResponse>> InitiateFlutterwaveCardPayment(FlutterwaveInitiateCardPaymentRequest request)
{
MyBankLog dbLog = new MyBankLog();
try
{
if (request.Amount <= 0)
{
throw new ArgumentException("Amount must be greater than zero.");
}
if (!IsValidEmail(request.Email))
{
throw new ArgumentException("Invalid email format.", nameof(request.Email));
}
var initiatePayment = await flutterwaveService.InitiatePaymentAsync(request, dbLog);
if (initiatePayment != null && initiatePayment.Status == "success")
{
return NewResult<FlutterwaveInitiateCardPaymentResponse>.Success(initiatePayment, "Payment initiation successful.");
}
else
{
return NewResult<FlutterwaveInitiateCardPaymentResponse>.Failed(null, "Payment initiation failed.");
}
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewResult<FlutterwaveInitiateCardPaymentResponse>.Error(null, $"Error while initiating payment : {ex.Message}");
}
}
public async Task<NewResult<PaystackPaymentInitiationResponse>> InitiatePayment(double amount, string email)
{
MyBankLog dbLog = new MyBankLog();
try
{
if (amount <= 0)
{
throw new ArgumentException("Amount must be greater than zero.");
}
if (string.IsNullOrWhiteSpace(email))
{
throw new ArgumentNullException(nameof(email), "Email cannot be null or empty.");
}
if (!IsValidEmail(email))
{
throw new ArgumentException("Invalid email format.", nameof(email));
}
var initiatePaymentRequest = new PaystackPaymentInitiationRequest()
{
Amount = amount,
Email = email
};
var initiatePayment = await paystackService.InitiatePaymentAsync(initiatePaymentRequest, dbLog);
// Validate the response
if (initiatePayment != null && initiatePayment.Status)
{
if (!string.IsNullOrWhiteSpace(initiatePayment.Data.AuthorizationUrl) && !string.IsNullOrWhiteSpace(initiatePayment.Data.Reference))
{
return NewResult<PaystackPaymentInitiationResponse>.Success(initiatePayment, "Payment successfully initiated.");
}
return NewResult<PaystackPaymentInitiationResponse>.Failed(null, "Payment initiation response missing critical data.");
}
return NewResult<PaystackPaymentInitiationResponse>.Failed(null, "Payment initiation failed.");
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewResult<PaystackPaymentInitiationResponse>.Error(null, $"Error while initiating payment : {ex.Message}");
}
}
public async Task<NewResult<PaystackPaymentVerificationResponse>> VerifyPayment(string reference)
{
MyBankLog dbLog = new MyBankLog();
try
{
if (string.IsNullOrWhiteSpace(reference))
{
throw new ArgumentNullException(nameof(reference), "Reference cannot be null or empty.");
}
var verifyPaymentRequest = new PaystackPaymentVerificationRequest()
{
Reference = reference
};
var verifyPayment = await paystackService.VerifyPaymentAsync(verifyPaymentRequest, dbLog);
if (verifyPayment != null && verifyPayment.Status)
{
if (verifyPayment.Data != null && !string.IsNullOrWhiteSpace(verifyPayment.Data.Status))
{
return NewResult<PaystackPaymentVerificationResponse>.Success(verifyPayment, "Payment successfully verified.");
}
return NewResult<PaystackPaymentVerificationResponse>.Failed(null, "Payment verification response missing critical data.");
}
return NewResult<PaystackPaymentVerificationResponse>.Failed(null, "Payment verification failed.");
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
return NewResult<PaystackPaymentVerificationResponse>.Error(null, $"Error while verifying payment: {ex.Message}");
}
}
private bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
}
}
Payment Controller
using Lacariz.Furniture.Service.Services.External_Service.Requests;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class PaymentController : BaseController
{
private readonly IPaymentService paymentService;
public PaymentController(IPaymentService paymentService)
{
this.paymentService = paymentService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/initiate-paystack-payment")]
[ApiVersion("1.0")]
public async Task<IActionResult> InitiatePaystackPayment(PaystackPaymentInitiationRequest request)
{
var response = await paymentService.InitiatePayment(request.Amount, request.Email);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/verify-paystack-payment")]
[ApiVersion("1.0")]
public async Task<IActionResult> VerifyPaystackPayment([FromQuery] PaystackPaymentVerificationRequest request)
{
var response = await paymentService.VerifyPayment(request.Reference);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/initiate-flutterwave-card-payment")]
[ApiVersion("1.0")]
public async Task<IActionResult> InitiateFlutterwavePayment([FromBody] FlutterwaveInitiateCardPaymentRequest request)
{
var response = await paymentService.InitiateFlutterwaveCardPayment(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/flutterwave-validate-charge")]
[ApiVersion("1.0")]
public async Task<IActionResult> FlutterwaveValidateCharge([FromBody] FlutterwaveValidateChargeRequest request)
{
var response = await paymentService.FlutterwaveValidateCharge(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/flutterwave-get-payment-status")]
[ApiVersion("1.0")]
public async Task<IActionResult> FlutterwaveVerifyPayment([FromQuery] FlutterwaveVerifyCardPaymentRequest request)
{
var response = await paymentService.FlutterwaveVerifyCardPayment(request);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Product Service for Lacariz Furniture E-commerce Application
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IFurnitureRepository
{
Task<IEnumerable<FurnitureItem>> GetAllFurnitureItemsAsync();
Task<FurnitureItem> GetFurnitureItemByIdAsync(string furnitureItemId);
Task<IEnumerable<FurnitureItem>> SearchFurnitureItemsAsync(FurnitureCategory? category, decimal minPrice, decimal maxPrice, string keyword);
Task<FurnitureItem> AddFurnitureItemAsync(FurnitureItem item);
Task<bool> UpdateFurnitureItemAsync(FurnitureItem item);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class FurnitureRepository : IFurnitureRepository
{
private readonly IMongoDBLogContext dbContext;
public FurnitureRepository(IMongoDBLogContext dbContext)
{
this.dbContext = dbContext;
}
public async Task<FurnitureItem> AddFurnitureItemAsync(FurnitureItem item)
{
await dbContext.FurnitureItems.InsertOneAsync(item);
return item;
}
public async Task<IEnumerable<FurnitureItem>> GetAllFurnitureItemsAsync()
{
var furnitures = await dbContext.FurnitureItems.FindAsync(_ => true);
return await furnitures.ToListAsync();
}
public async Task<FurnitureItem> GetFurnitureItemByIdAsync(string furnitureItemId)
{
return await dbContext.FurnitureItems.Find(item => item.Id == furnitureItemId).FirstOrDefaultAsync();
}
//public async Task<IEnumerable<FurnitureItem>> SearchFurnitureItemsAsync(string category, decimal minPrice, decimal maxPrice, string keyword)
//{
// var filterBuilder = Builders<FurnitureItem>.Filter;
// var filter = filterBuilder.Empty;
// if (!string.IsNullOrWhiteSpace(category))
// filter &= filterBuilder.Eq(item => item.Category, category);
// if (minPrice >= 0 && maxPrice > minPrice)
// filter &= filterBuilder.Gte(item => item.Price, minPrice) & filterBuilder.Lte(item => item.Price, maxPrice);
// if (!string.IsNullOrWhiteSpace(keyword))
// filter &= filterBuilder.Text(keyword);
// return await dbContext.FurnitureItems.Find(filter).ToListAsync();
//}
public async Task<IEnumerable<FurnitureItem>> SearchFurnitureItemsAsync(FurnitureCategory? category, decimal minPrice, decimal maxPrice, string keyword)
{
var filterBuilder = Builders<FurnitureItem>.Filter;
var filter = filterBuilder.Empty;
if (category.HasValue)
filter &= filterBuilder.Eq(item => item.Category, category);
if (minPrice >= 0 && maxPrice > minPrice)
filter &= filterBuilder.Gte(item => item.Price, minPrice) & filterBuilder.Lte(item => item.Price, maxPrice);
if (!string.IsNullOrWhiteSpace(keyword))
filter &= filterBuilder.Text(keyword);
return await dbContext.FurnitureItems.Find(filter).ToListAsync();
}
public async Task<bool> UpdateFurnitureItemAsync(FurnitureItem item)
{
var filter = Builders<FurnitureItem>.Filter.Eq(f => f.Id, item.Id);
var update = Builders<FurnitureItem>.Update
.Set(f => f.Name, item.Name)
.Set(f => f.Description, item.Description)
.Set(f => f.Price, item.Price)
.Set(f => f.StockQuantity, item.StockQuantity)
.Set(f => f.Category, item.Category);
var result = await dbContext.FurnitureItems.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}
}
}
Product Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IFurnitureService
{
Task<NewResult<IEnumerable<FurnitureItem>>> GetAllFurnitureItemsAsync();
Task<NewResult<FurnitureItem>> GetFurnitureItemByIdAsync(string furnitureItemId);
Task<NewResult<IEnumerable<FurnitureItem>>> SearchFurnitureItemsAsync(FurnitureCategory category, decimal minPrice, decimal maxPrice, string keyword);
Task<NewResult<FurnitureItem>> AddFurnitureItemAsync(FurnitureItem item);
Task<NewResult<FurnitureItem>> UpdateFurnitureItemAsync(FurnitureItem item);
}
}
using Lacariz.Furniture.Data.Repositories.Implementations;
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using Lacariz.Furniture.Service.Services.Interfaces;
using System.DirectoryServices;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class FurnitureService : IFurnitureService
{
private readonly IFurnitureRepository furnitureRepository;
public FurnitureService(IFurnitureRepository furnitureRepository)
{
this.furnitureRepository = furnitureRepository;
}
public async Task<NewResult<FurnitureItem>> AddFurnitureItemAsync(FurnitureItem item)
{
try
{
var newItem = await furnitureRepository.AddFurnitureItemAsync(item);
if (newItem != null)
{
return NewResult<FurnitureItem>.Success(newItem, "Furniture item added successfully.");
}
return NewResult<FurnitureItem>.Failed(null, "Failure to add item.");
}
catch (Exception ex)
{
return NewResult<FurnitureItem>.Error(null, $"Error occurred: {ex.Message}");
}
}
public async Task<NewResult<IEnumerable<FurnitureItem>>> GetAllFurnitureItemsAsync()
{
try
{
var furnitureItems = await furnitureRepository.GetAllFurnitureItemsAsync();
/// return furnitureItems ?? Enumerable.Empty<FurnitureItem>();
if (furnitureItems == null)
{
return NewResult<IEnumerable<FurnitureItem>>.Failed(null, "Kindly add items");
}
return NewResult<IEnumerable<FurnitureItem>>.Success(furnitureItems, "Items retrieved successfully");
}
catch (Exception ex)
{
return NewResult<IEnumerable<FurnitureItem>>.Error(null, $"Error retrieving items: {ex.Message}");
}
}
public async Task<NewResult<FurnitureItem>> GetFurnitureItemByIdAsync(string furnitureItemId)
{
try
{
var furnitureItem = await furnitureRepository.GetFurnitureItemByIdAsync(furnitureItemId);
if (furnitureItem == null)
{
return NewResult<FurnitureItem>.Failed(null, "Item doesn't exist");
}
return NewResult<FurnitureItem>.Success(furnitureItem, "Item retrieved successfully");
}
catch (Exception ex)
{
return NewResult<FurnitureItem>.Error(null, $"Error while retrieving item: {ex.Message} ");
}
}
public async Task<NewResult<IEnumerable<FurnitureItem>>> SearchFurnitureItemsAsync(FurnitureCategory category, decimal minPrice, decimal maxPrice, string keyword)
{
try
{
var searchResult = await furnitureRepository.SearchFurnitureItemsAsync(category, minPrice, maxPrice, keyword);
if (searchResult == null)
{
return NewResult<IEnumerable<FurnitureItem>>.Failed(null, "unable to provide search results");
}
return NewResult<IEnumerable<FurnitureItem>>.Success(searchResult, "Search carried out successfully");
}
catch (Exception ex)
{
return NewResult<IEnumerable<FurnitureItem>>.Error(null, $"An error occured while carrying out search: {ex.Message}");
}
}
public async Task<NewResult<FurnitureItem>> UpdateFurnitureItemAsync(FurnitureItem item)
{
try
{
var updated = await furnitureRepository.UpdateFurnitureItemAsync(item);
if (updated)
{
return NewResult<FurnitureItem>.Success(item, "Furniture item updated successfully.");
}
else
{
return NewResult<FurnitureItem>.Failed(null, "Failed to update furniture item.");
}
}
catch (Exception ex)
{
return NewResult<FurnitureItem>.Failed(null, $"Error occurred: {ex.Message}");
}
}
}
}
Product Controller
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Domain.Enum;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class ProductController : BaseController
{
private readonly IFurnitureService furnitureService;
private readonly IShoppingCartService shoppingCartService;
public ProductController(IFurnitureService furnitureService, IShoppingCartService shoppingCartService)
{
this.furnitureService = furnitureService;
this.shoppingCartService = shoppingCartService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/admin/add-products")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> AddFurnitureItems([FromBody] FurnitureItem item)
{
var response = await furnitureService.AddFurnitureItemAsync(item);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-all-products")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetAllItems()
{
var response = await furnitureService.GetAllFurnitureItemsAsync();
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-product-by-id")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetItemById(string productId)
{
var response = await furnitureService.GetFurnitureItemByIdAsync(productId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/admin/update-product")]
[Authorize(Policy = "AdminOnly")]
[ApiVersion("1.0")]
public async Task<IActionResult> UpdateFurnitureItems([FromBody] FurnitureItem item)
{
var response = await furnitureService.UpdateFurnitureItemAsync(item);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/search-product")]
[ApiVersion("1.0")]
public async Task<IActionResult> SearchProduct(FurnitureCategory category, decimal minPrice, decimal maxPrice, string keyword)
{
var response = await furnitureService.SearchFurnitureItemsAsync(category, minPrice, maxPrice, keyword);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpPost("api/v{version:apiVersion}/[controller]/add-item-to-cart")]
[ApiVersion("1.0")]
public async Task<IActionResult> AddItemToCart(string userId, ShoppingCartItem item)
{
var response = await shoppingCartService.AddItemToCartAsync(userId, item);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpDelete("api/v{version:apiVersion}/[controller]/clear-cart-items")]
[ApiVersion("1.0")]
public async Task<IActionResult> ClearCartAsync(string userId)
{
var response = await shoppingCartService.ClearCartAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpDelete("api/v{version:apiVersion}/[controller]/delete-cart-item")]
[ApiVersion("1.0")]
public async Task<IActionResult> DeleteItem(string userId, string productId)
{
var response = await shoppingCartService.DeleteItemAsync(userId, productId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-cart-item")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetCartItem(string userId, string productId)
{
var response = await shoppingCartService.GetCartItemAsync(userId, productId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-all-cart-items")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetCartItems(string userId)
{
var response = await shoppingCartService.GetCartItemsAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the Push Notification Service for Lacariz Furniture E-commerce Application
using Lacariz.Furniture.Domain.Common.Generics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IPushNotificationService
{
Task<NewResult<string>> SendPushNotificationAsync(string userId, string body);
}
}
using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Service.Services.Interfaces;
using System.Text;
using Message = FirebaseAdmin.Messaging.Message;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class PushNotificationService : IPushNotificationService
{
private readonly IUserRepository userRepository;
public PushNotificationService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public async Task<NewResult<string>> SendPushNotificationAsync(string userId, string body)
{
try
{
var user = await userRepository.GetUserById(userId);
//if (user == null || string.IsNullOrEmpty(user.DeviceToken))
// throw new ArgumentNullException(nameof(user.DeviceToken), "User device token cannot be null or empty.");
var message = new Message
{
Token = GenerateMockDeviceToken(),
Notification = new Notification
{
Title = "Order Update",
Body = body
}
};
// Ensure Firebase is initialized
if (FirebaseApp.DefaultInstance == null)
{
FirebaseApp.Create(new AppOptions
{
Credential = GoogleCredential.FromFile("Properties/NotificationFile.json")
});
}
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
return NewResult<string>.Success(response, "Push notification sent successfully.");
}
catch (Exception ex)
{
return NewResult<string>.Failed(null, $"Error occurred: {ex.Message}");
}
}
public string GenerateMockDeviceToken()
{
// Define the characters allowed in the device token
const string allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// Define the length of the device token
const int tokenLength = 140;
// Use a StringBuilder to construct the device token
StringBuilder tokenBuilder = new StringBuilder();
// Use a random number generator to select characters from the allowed set
Random random = new Random();
for (int i = 0; i < tokenLength; i++)
{
int index = random.Next(allowedChars.Length);
tokenBuilder.Append(allowedChars[index]);
}
// Return the generated device token
return tokenBuilder.ToString();
}
}
}
PushNotification Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class PushNotificationController : BaseController
{
private readonly IPushNotificationService pushNotificationService;
public PushNotificationController(IPushNotificationService pushNotificationService)
{
this.pushNotificationService = pushNotificationService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/send-push-notification")]
[ApiVersion("1.0")]
public async Task<IActionResult> SendPushNotification([FromBody] PushNotificationRequest request)
{
var response = await pushNotificationService.SendPushNotificationAsync(request.UserId, request.body);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
Implementing the WishList Service for Lacariz Furniture E-commerce Application
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Data.Repositories.Interfaces
{
public interface IWishlistRepository
{
Task<WishlistItem> AddItemToWishlistAsync(string userId, string furnitureItemId);
Task<bool> RemoveItemFromWishlistAsync(string userId, string furnitureItemId);
Task<IEnumerable<WishlistItem>> GetUserWishlistAsync(string userId);
Task<bool> ClearUserWishlistAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Lacariz.Furniture.Data.Repositories.Implementations
{
public class WishlistRepository : IWishlistRepository
{
private readonly IMongoDBLogContext dbContext;
public WishlistRepository(IMongoDBLogContext dbContext)
{
this.dbContext = dbContext;
}
public async Task<WishlistItem> AddItemToWishlistAsync(string userId, string furnitureItemId)
{
var filter = Builders<WishlistItem>.Filter.And(
Builders<WishlistItem>.Filter.Eq(w => w.UserId, userId),
Builders<WishlistItem>.Filter.Eq(w => w.FurnitureItemId, furnitureItemId)
);
var existingWishlistItem = await dbContext.WishlistItems.Find(filter).FirstOrDefaultAsync();
if (existingWishlistItem == null)
{
var wishlistItem = new WishlistItem
{
UserId = userId,
FurnitureItemId = furnitureItemId
};
await dbContext.WishlistItems.InsertOneAsync(wishlistItem);
return wishlistItem;
}
else
{
return existingWishlistItem;
}
}
public async Task<bool> ClearUserWishlistAsync(string userId)
{
try
{
var filter = Builders<WishlistItem>.Filter.Eq(w => w.UserId, userId);
var result = await dbContext.WishlistItems.DeleteManyAsync(filter);
return result.IsAcknowledged && result.DeletedCount > 0;
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error occurred while clearing user wishlist: {ex.Message}");
return false;
}
}
public async Task<IEnumerable<WishlistItem>> GetUserWishlistAsync(string userId)
{
try
{
var filter = Builders<WishlistItem>.Filter.Eq(w => w.UserId, userId);
var wishlistItems = await dbContext.WishlistItems.Find(filter).ToListAsync();
return wishlistItems;
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error occurred while retrieving user wishlist: {ex.Message}");
throw;
}
}
public async Task<bool> RemoveItemFromWishlistAsync(string userId, string furnitureItemId)
{
try
{
var filter = Builders<WishlistItem>.Filter.And(
Builders<WishlistItem>.Filter.Eq(w => w.UserId, userId),
Builders<WishlistItem>.Filter.Eq(w => w.FurnitureItemId, furnitureItemId)
);
var result = await dbContext.WishlistItems.DeleteOneAsync(filter);
return result.DeletedCount > 0;
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error occurred while removing item from wishlist: {ex.Message}");
throw;
}
}
}
}
WishList Service
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IWishlistService
{
Task<NewResult<WishlistItem>> AddItemToWishlistAsync(string userId, string furnitureItemId);
Task<NewResult<bool>> RemoveItemFromWishlistAsync(string userId, string furnitureItemId);
Task<NewResult<IEnumerable<WishlistItem>>> GetUserWishlistAsync(string userId);
Task<NewResult<bool>> ClearUserWishlistAsync(string userId);
}
}
using Lacariz.Furniture.Data.Repositories.Implementations;
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Domain.Entities;
using Lacariz.Furniture.Service.Services.Interfaces;
using MySqlX.XDevAPI.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class WishlistService : IWishlistService
{
private readonly IWishlistRepository wishlistRepository;
public WishlistService(IWishlistRepository wishlistRepository)
{
this.wishlistRepository = wishlistRepository;
}
public async Task<NewResult<WishlistItem>> AddItemToWishlistAsync(string userId, string furnitureItemId)
{
NewResult<WishlistItem> result = new NewResult<WishlistItem>();
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId));
if (string.IsNullOrEmpty(furnitureItemId))
throw new ArgumentNullException(nameof(furnitureItemId));
var addItem = await wishlistRepository.AddItemToWishlistAsync(userId, furnitureItemId);
if (addItem != null)
{
return NewResult<WishlistItem>.Success(addItem, "Item added to wishlist successfully");
}
return NewResult<WishlistItem>.Failed(null, "Item added to wishlist successfully");
}
catch (Exception ex)
{
return NewResult<WishlistItem>.Error(null, $"Error while clearing cart: {ex.Message}");
}
}
public async Task<NewResult<bool>> ClearUserWishlistAsync(string userId)
{
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId));
bool success = await wishlistRepository.ClearUserWishlistAsync(userId);
if (!success)
{
return NewResult<bool>.Failed(false, "Error while clearing wishlist");
}
return NewResult<bool>.Success(true, "Wishlist cleared successfully");
}
catch (Exception ex)
{
return NewResult<bool>.Error(false, $"Error while clearing cart: {ex.Message}");
}
}
public async Task<NewResult<IEnumerable<WishlistItem>>> GetUserWishlistAsync(string userId)
{
try
{
var wishlistItems = await wishlistRepository.GetUserWishlistAsync(userId);
if (wishlistItems != null)
{
return NewResult<IEnumerable<WishlistItem>>.Success(wishlistItems, "User wishlist retrieved successfully.");
}
else
{
return NewResult<IEnumerable<WishlistItem>>.Failed(null, "Wishlist not found.");
}
}
catch (Exception ex)
{
return NewResult<IEnumerable<WishlistItem>>.Error(null, $"An error occured while retrieving user wishlist: {ex.Message}");
}
}
public async Task<NewResult<bool>> RemoveItemFromWishlistAsync(string userId, string furnitureItemId)
{
try
{
if (string.IsNullOrEmpty(userId))
throw new ArgumentNullException(nameof(userId), "User ID cannot be null or empty.");
if (string.IsNullOrEmpty(furnitureItemId))
throw new ArgumentNullException(nameof(furnitureItemId), "Furniture item ID cannot be null or empty.");
bool success = await wishlistRepository.RemoveItemFromWishlistAsync(userId, furnitureItemId);
if (success)
{
return NewResult<bool>.Success(true, "Item removed from wishlist successfully.");
}
else
{
return NewResult<bool>.Failed(false, "Failed to remove item from wishlist.");
}
}
catch (Exception ex)
{
return NewResult<bool>.Error(false, $"Error occurred: {ex.Message}");
}
}
}
}
WishList Controller
using Lacariz.Furniture.Domain.DataTransferObjects;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.API.Controllers.v1
{
public class WishlistController : BaseController
{
private readonly IWishlistService wishlistService;
public WishlistController(IWishlistService wishlistService)
{
this.wishlistService = wishlistService;
}
[HttpPost("api/v{version:apiVersion}/[controller]/add-item-to-wishlist")]
[ApiVersion("1.0")]
public async Task<IActionResult> AddItemToWishlist(WishlistRequest request)
{
var response = await wishlistService.AddItemToWishlistAsync(request.UserId, request.FurnitureItemId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpDelete("api/v{version:apiVersion}/[controller]/remove-item-from-Wishlist")]
[ApiVersion("1.0")]
public async Task<IActionResult> RemoveItemFromWishlist(WishlistRequest request)
{
var response = await wishlistService.RemoveItemFromWishlistAsync(request.UserId, request.FurnitureItemId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpGet("api/v{version:apiVersion}/[controller]/get-user-wishlist")]
[ApiVersion("1.0")]
public async Task<IActionResult> GetUserWishlist(string userId)
{
var response = await wishlistService.GetUserWishlistAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
[HttpDelete("api/v{version:apiVersion}/[controller]/clear-user-wishlist")]
[ApiVersion("1.0")]
public async Task<IActionResult> ClearUserWishlist(string userId)
{
var response = await wishlistService.ClearUserWishlistAsync(userId);
return response.ResponseCode switch
{
"00" => Ok(response),
"99" => BadRequest(response),
"77" => StatusCode(417, response), // DUPLICATE
_ => StatusCode(500, response)
};
}
}
}
EMAIL SERVICE
using Lacariz.Furniture.Domain.Common.Generics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.Interfaces
{
public interface IEmailService
{
Task<NewResult<string>> SendActivationEmail(string emailAddress, string verificationCode);
}
}
using System.Net.Mail;
using System.Net;
using Lacariz.Furniture.Domain.Common.Generics;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.Service.Services.Implementations
{
public class EmailService : IEmailService
{
public readonly IConfiguration configuration;
public EmailService(IConfiguration configuration)
{
this.configuration = configuration;
}
public async Task<NewResult<string>> SendActivationEmail(string emailAddress, string verificationCode)
{
NewResult<string> result = new NewResult<string>();
try
{
// Get SMTP server settings from appsettings
var smtpServer = configuration["EmailSettings:SmtpHost"];
var port = int.Parse(configuration["EmailSettings:SmtpPort"]);
var username = configuration["EmailSettings:SmtpUser"];
var password = configuration["EmailSettings:SmtpPass"];
using (var client = new SmtpClient())
{
// Specify SMTP server settings
client.Host = smtpServer;
client.Port = port;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(username, password);
client.EnableSsl = true;
// Create and configure the email message
var message = new MailMessage();
message.From = new MailAddress(username); // Sender email address
message.To.Add(emailAddress);
message.Subject = "Account Activation"; // Email subject
message.Body = $"Please click the following link to activate your account: {verificationCode}"; // Email body
// Send email asynchronously
await client.SendMailAsync(message);
}
return NewResult<string>.Success(null, "Email sent successfully");
}
catch (Exception)
{
return NewResult<string>.Failed(null, "unable to send email");
}
}
}
}
DOMAIN LAYER : Models are listed below
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using Lacariz.Furniture.Domain.Enum;
namespace Lacariz.Furniture.Domain.Entities
{
public class User
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public bool isActivated { get; set; } = false;
public UserRole Role { get; set; } // Use UserRole enum for Role property
}
}
using Lacariz.Furniture.Domain.Enum;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class Admin
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string AdminId { get; set; } = Guid.NewGuid().ToString();
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string ProfilePictureUrl { get; set; }
public string AdminLoginId { get; set; } = "A86478927";
public bool isActivated { get; set; } = false;
public UserRole Role { get; set; } // Use UserRole enum for Role property
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class CustomerInquiry
{
public string Id { get; set; }
public string UserId { get; set; }
public string Subject { get; set; }
public string Message { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsResolved { get; set; }
}
}
using Lacariz.Furniture.Domain.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class FurnitureItem
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public FurnitureCategory Category { get; set; }
}
}
namespace Lacariz.Furniture.Domain.Entities
{
public class MyBankLog
{
public string ServiceName { get; set; }
public string Endpoint { get; set; }
public string UserId { get; set; }
public string ChannelId { get; set; }
public string RequestDate { get; set; }
public string RequestDetails { get; set; }
public string ResponseDate { get; set; }
public string UserToken { get; set; }
public string Response { get; set; } = "Failed";
public string ResponseDetails { get; set; }
public string AdditionalInformation { get; set; }
public string Amount { get; set; }
}
}
using Lacariz.Furniture.Domain.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class Order
{
public string Id { get; set; }
public string UserId { get; set; }
public List<OrderItem> Items { get; set; }
public DateTime OrderDate { get; set; }
public OrderStatus Status { get; set; }
public decimal TotalAmount { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class OrderItem
{
public string FurnitureItemId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
}
using Lacariz.Furniture.Domain.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class PreOrder
{
public string Id { get; set; }
public string UserId { get; set; }
public string FurnitureItemId { get; set; }
public DateTime PreOrderDate { get; set; }
public PreOrderStatus Status { get; set; }
public int Quantity { get; set; }
}
}
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class ShoppingCart
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string UserId { get; set; } // Add UserId property
public List<ShoppingCartItem> Items { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class ShoppingCartItem
{
public string FurnitureItemId { get; set; }
public int Quantity { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Entities
{
public class WishlistItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string FurnitureItemId { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class ActivateAccountRequest
{
public string EmailAddress { get; set; }
public string ActivationCode { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class AdminLoginRequest
{
public string AdminLoginId { get; set; }
public string Password { get; set; }
}
}
using Lacariz.Furniture.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class CreateOrderRequest
{
public string UserId { get; set; }
public List<OrderItem> items { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class CreatePreOrderRequest
{
public string UserId { get; set; }
public string FurnitureItemId { get; set; }
public int Quantity { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class LoginRequest
{
public string EmailAddress { get; set; }
public string Password { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class PushNotificationRequest
{
public string UserId { get; set; }
public string body { get; set; }
// public string DeviceToken { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class ResetPasswordRequest
{
public string EmailAddress { get; set; }
public string VerificationCode { get; set; }
public string NewPassword { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class ResetPasswordRequestDto
{
public string EmailAddress { get; set; }
public string NewPassword { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class StockLevelRequest
{
public string FurnitureItemId { get; set; }
public int NewStockLevel { get; set;}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.DataTransferObjects
{
public class WishlistRequest
{
public string UserId { get; set; }
public string FurnitureItemId { get; set; }
}
}
ENUM
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Enum
{
public enum FurnitureCategory
{
LivingRoom,
Bedroom,
DiningRoom,
Office,
Outdoor,
Children
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Enum
{
public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Enum
{
public enum PreOrderStatus
{
Pending,
Fulfilled,
Cancelled
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Domain.Enum
{
public enum UserRole
{
NormalUser,
Admin
}
}
EXTERNAL SERVICE ENTITIES
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Requests
{
public class FlutterwaveInitiateCardPaymentRequest
{
[JsonProperty("card_number")]
public string CardNumber { get; set; }
[JsonProperty("cvv")]
public string CVV { get; set; }
[JsonProperty("expiry_month")]
public string ExpiryMonth { get; set; }
[JsonProperty("expiry_year")]
public string ExpiryYear { get; set; }
[JsonProperty("currency")]
public string Currency { get; set; }
[JsonProperty("amount")]
public double Amount { get; set; }
[JsonProperty("fullname")]
public string FullName { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("tx_ref")]
public string TransactionReference { get; set; }
[JsonProperty("redirect_url")]
public string? RedirectUrl { get; set; } = "https://www.flutterwave.ng";
[JsonProperty("authorization")]
public Authorization Authorization { get; set; }
}
public class Authorization
{
[JsonProperty("mode")]
public string Mode { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("state")]
public string State { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("zipcode")]
public string Zipcode { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Requests
{
public class FlutterwaveValidateChargeRequest
{
[JsonProperty("otp")]
public string Otp { get; set; }
[JsonProperty("flw_ref")]
public string Flw_ref { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Requests
{
public class FlutterwaveVerifyCardPaymentRequest
{
public string TransactionId { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Requests
{
public class PaystackPaymentInitiationRequest
{
[JsonProperty("amount")]
public double Amount { get; set; }
[JsonProperty("email")]
public string? Email { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Requests
{
public class PaystackPaymentVerificationRequest
{
public string Reference { get; set; } = string.Empty;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyResponse
{
public class Data
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tx_ref")]
public string TxRef { get; set; }
[JsonProperty("flw_ref")]
public string FlwRef { get; set; }
}
public class FlutterwaveValidateChargeResponse
{
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("data")]
public Data Data { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave.VerifyPaymentResponse
{
public class Data
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tx_ref")]
public string TxRef { get; set; }
[JsonProperty("flw_ref")]
public string FlwRef { get; set; }
}
public class FlutterwaveVerifyCardPaymentResponse
{
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("data")]
public Data Data { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Responses.Flutterwave
{
public class Authorization
{
[JsonProperty("mode")]
public string Mode { get; set; }
[JsonProperty("endpoint")]
public string Endpoint { get; set; }
}
public class Data
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tx_ref")]
public string TxRef { get; set; }
[JsonProperty("flw_ref")]
public string FlwRef { get; set; }
[JsonProperty("processor_response")]
public string ProcessorResponse { get; set; }
}
public class Meta
{
[JsonProperty("authorization")]
public Authorization Authorization { get; set; }
}
public class FlutterwaveInitiateCardPaymentResponse
{
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("data")]
public Data Data { get; set; }
[JsonProperty("meta")]
public Meta Meta { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Responses.Verification
{
public class Authorization
{
[JsonProperty("authorization_code")]
public string AuthorizationCode { get; set; }
[JsonProperty("bin")]
public string Bin { get; set; }
[JsonProperty("last4")]
public string Last4 { get; set; }
[JsonProperty("exp_month")]
public string ExpMonth { get; set; }
[JsonProperty("exp_year")]
public string ExpYear { get; set; }
[JsonProperty("channel")]
public string Channel { get; set; }
[JsonProperty("card_type")]
public string CardType { get; set; }
[JsonProperty("bank")]
public string Bank { get; set; }
[JsonProperty("country_code")]
public string CountryCode { get; set; }
[JsonProperty("brand")]
public string Brand { get; set; }
[JsonProperty("reusable")]
public bool Reusable { get; set; }
[JsonProperty("signature")]
public string Signature { get; set; }
[JsonProperty("account_name")]
public object AccountName { get; set; }
}
public class Customer
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("first_name")]
public object FirstName { get; set; }
[JsonProperty("last_name")]
public object LastName { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("customer_code")]
public string CustomerCode { get; set; }
[JsonProperty("phone")]
public object Phone { get; set; }
[JsonProperty("metadata")]
public object Metadata { get; set; }
[JsonProperty("risk_action")]
public string RiskAction { get; set; }
[JsonProperty("international_format_phone")]
public object InternationalFormatPhone { get; set; }
}
public class Data
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("domain")]
public string Domain { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("reference")]
public string Reference { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
[JsonProperty("message")]
public object Message { get; set; }
[JsonProperty("gateway_response")]
public string GatewayResponse { get; set; }
[JsonProperty("paid_at")]
public DateTime? PaidAt { get; set; }
[JsonProperty("created_at")]
public DateTime? CreatedAt { get; set; }
[JsonProperty("channel")]
public string Channel { get; set; }
[JsonProperty("currency")]
public string Currency { get; set; }
[JsonProperty("ip_address")]
public string IpAddress { get; set; }
[JsonProperty("metadata")]
public string Metadata { get; set; }
[JsonProperty("log")]
public Log Log { get; set; }
[JsonProperty("fees")]
public int? Fees { get; set; }
[JsonProperty("fees_split")]
public object? FeesSplit { get; set; }
[JsonProperty("authorization")]
public Authorization Authorization { get; set; }
[JsonProperty("customer")]
public Customer Customer { get; set; }
[JsonProperty("plan")]
public object Plan { get; set; }
[JsonProperty("split")]
public Split Split { get; set; }
[JsonProperty("order_id")]
public object OrderId { get; set; }
[JsonProperty("requested_amount")]
public int RequestedAmount { get; set; }
[JsonProperty("pos_transaction_data")]
public object PosTransactionData { get; set; }
[JsonProperty("source")]
public object Source { get; set; }
[JsonProperty("fees_breakdown")]
public object FeesBreakdown { get; set; }
[JsonProperty("transaction_date")]
public DateTime TransactionDate { get; set; }
[JsonProperty("plan_object")]
public PlanObject PlanObject { get; set; }
[JsonProperty("subaccount")]
public Subaccount Subaccount { get; set; }
}
public class History
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("time")]
public int Time { get; set; }
}
public class Log
{
[JsonProperty("start_time")]
public int StartTime { get; set; }
[JsonProperty("time_spent")]
public int TimeSpent { get; set; }
[JsonProperty("attempts")]
public int Attempts { get; set; }
[JsonProperty("errors")]
public int Errors { get; set; }
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("mobile")]
public bool Mobile { get; set; }
[JsonProperty("input")]
public List<object> Input { get; set; }
[JsonProperty("history")]
public List<History> History { get; set; }
}
public class PlanObject
{
}
public class PaystackPaymentVerificationResponse
{
[JsonProperty("status")]
public bool Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("data")]
public Data Data { get; set; }
}
public class Split
{
}
public class Subaccount
{
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Lacariz.Furniture.Service.Services.External_Service.Responses
{
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Data
{
[JsonProperty("authorization_url")]
public string AuthorizationUrl { get; set; }
[JsonProperty("access_code")]
public string AccessCode { get; set; }
[JsonProperty("reference")]
public string Reference { get; set; }
}
public class PaystackPaymentInitiationResponse
{
[JsonProperty("status")]
public bool Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("data")]
public Data Data { get; set; }
}
}
SERVICE REGISTRATION
using Lacariz.Furniture.Data.Repositories.Implementations;
using Lacariz.Furniture.Data.Repositories.Interfaces;
using Lacariz.Furniture.Domain.Config.Implementations;
using Lacariz.Furniture.Domain.Config.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using System.Configuration;
namespace Lacariz.Furniture.Data;
public static class ServiceRegistration
{
public static IServiceCollection AddDataDependencies(this IServiceCollection services, IConfiguration configuration)
{
try
{
services.AddScoped<ISampleRepository, SampleRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IFurnitureRepository, FurnitureRepository>();
services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IPreOrderRepository, PreOrderRepository>();
services.AddScoped<IWishlistRepository, WishlistRepository>();
services.AddScoped<IInventoryRepository, InventoryRepository>();
services.AddScoped<ICustomerSupportRepository, CustomerSupportRepository>();
services.AddSingleton<IMongoClient, MongoClient>(sp => new MongoClient(configuration["MongoDbSettings:ConnectionString"]));
services.AddSingleton<IMongoDbConfig, MongoDbConfig>(
sp => new MongoDbConfig(configuration.GetSection("MongoDbSettings:ConnectionString").Value,
configuration.GetSection("MongoDbSettings:DatabaseName").Value));
services.AddScoped<IMongoDBLogContext, MongoDBLogContext>();
services.AddScoped<IMyBankLogRepository, MyBankLogRepository>();
return services;
}
catch (Exception ex)
{
return null;
}
}
}
using Lacariz.Furniture.Service.Helpers.Implementations;
using Lacariz.Furniture.Service.Helpers.Interfaces;
using Lacariz.Furniture.Service.Services.External_Service.Implementations;
using Lacariz.Furniture.Service.Services.External_Service.Interfaces;
using Lacariz.Furniture.Service.Services.Implementations;
using Lacariz.Furniture.Service.Services.Interfaces;
namespace Lacariz.Furniture.Service;
public static class ServiceRegistration
{
public static IServiceCollection AddServiceDependencies(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<ISampleService, SampleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<IShoppingCartService, ShoppingCartService>();
services.AddScoped<IFurnitureService, FurnitureService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IPreOrderService, PreOrderService>();
services.AddScoped<IWishlistService, WishlistService>();
services.AddScoped<IInventoryService, InventoryService>();
services.AddScoped<IPushNotificationService, PushNotificationService>();
services.AddScoped<ICustomerSupportService, CustomerSupportService>();
services.AddScoped<IPaystackService, PaystackService>();
services.AddScoped<IFlutterwaveService, FlutterwaveService>();
services.AddScoped<IPaymentService, PaymentService>();
services.AddScoped<IEncryptionHelper, EncryptionHelper>();
services.AddScoped<IAuthService, AuthService>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IRestHelper, RestHelper>();
services.AddAutoMapper(typeof(ServiceRegistration));
services.AddControllersWithViews();
return services;
}
}
FIREBASE INITIALIZATION
namespace Lacariz.Furniture.API
{
using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
public static class FirebaseInitializer
{
public static void Initialize()
{
FirebaseApp.Create(new AppOptions
{
Credential = GoogleCredential.FromFile("appsettings.json/appsettings.Development.json")
});
}
}
}
EXTENSION
public static class Extensions
{
public static WebApplicationBuilder AddConfiguration(this WebApplicationBuilder builder)
{
builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
return builder;
}
}
LinkedIn Account
: LinkedIn
Twitter Account
: Twitter
Credit: Graphics sourced from Medium
Top comments (14)
Hey, great content!
When i copy paste Program.cs , i get errors for builder.Services.AddDataDependencies(builder.Configuration);
builder.Services.AddServiceDependencies(builder.Configuration);
Do you happen to know why?
Hi Filip,
Thank you for reaching out and bringing this to my attention. The errors you're encountering when copying Program.cs to the web API project could be due to missing packages or configurations specific to our original setup. It's possible that the methods
AddDataDependencies
andAddServiceDependencies
rely on dependencies not yet installed or configured in the web API project.I recommend checking if all necessary packages are installed and configurations are correctly set up in the web API project. If the error persists after verifying these aspects, we can then schedule a Google Meet to dive into this further. Please keep me updated on your progress.
Hey man, thanks for showing me that.
I ve learned a lot from this project.
Just one more thing i would love to get an answer?
Could you help me with interfaces IEncryptionHelper and IAuthService?
What libraries, packages, did you use to implement this?
.
Do you maybe have whole source code?
Of course, I do have the whole source code since it's my project.
Well, then it would be great if you could share link :)
I have a question:
why you're rethrowing exceptions in the UserRepository ** class ?
imho it doesn't have any sense, it's even worse when you do **throw ex //losing the stacktrace info
Hello, this article is very nice and helpful. Could you please share the source code link?
Hi Odumosu Matthew,
Top, very nice and helpful !
Thanks for sharing.
Thank you for the positive feedback! I'm really pleased to hear that you found it helpful. If there's anything specific you'd like to discuss or any suggestions you have, I'd love to hear them.
what about frontend?
Thanks this is very helpful :)
I really appreciate your feedback, especially coming from a fellow .NET engineer! If you have any insights or suggestions based on your experience, I'd love to hear them.