Skip to content

Commit

Permalink
feat: Update clean architecture setup repo structure (#21)
Browse files Browse the repository at this point in the history
* Add EF core tools package

* Modify the builder and services setup

* Add base entity and modify structure for domain

* Modify DTOs model directory

* Add base response for APIs and configure model validation error response

* Update structure for records in DTO to have data annotations

* Add config value for auth token expiry and update formatting for user repo

* Update controllers to use base response class

* Update field datatype for base entity

* Add automapper service

* Recreate intial migration with updated fields

* Update token generator to use jwt key from appsetting

* Add regex model validator for password

* Add summary comments for API

* Remove package from domain and update entities

* Add services project library

* Move services to services project and update project references

* Update base entity

* Update html template path

* Format codebase

* Replace jwt key reading from env

* Update email sending service file paths

* Add default values for base entity

* Add xml comments to classes
  • Loading branch information
shreyanshJain852 authored Feb 27, 2024
1 parent 8ab2bbc commit 9c29387
Show file tree
Hide file tree
Showing 52 changed files with 700 additions and 269 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using DotnetFoundation.Application.DTO.AuthenticationDTO;
using DotnetFoundation.Application.Interfaces.Services;
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.AuthenticationDTO;
using DotnetFoundation.Application.Models.Enums;
using Microsoft.AspNetCore.Mvc;

namespace DotnetFoundation.Api.Controllers;
Expand All @@ -14,29 +16,75 @@ public AuthenticationController(IAuthenticationService authenticationService)
_authenticationService = authenticationService;
}

/// <summary>
/// User registration.
/// </summary>
/// <param name="request">New user registration request</param>
[HttpPost("register")]
public async Task<IActionResult> RegisterAsync(RegisterRequest request)
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> RegisterAsync(RegisterRequest request)
{
AuthenticationResponse result = await _authenticationService.RegisterAsync(request).ConfigureAwait(false);
return Ok(result);
BaseResponse<AuthenticationResponse> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.RegisterAsync(request).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}

/// <summary>
/// User login.
/// </summary>
/// <param name="request">User login request</param>
[HttpPost("login")]
public async Task<IActionResult> LoginAsync(LoginRequest request)
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> LoginAsync(LoginRequest request)
{
AuthenticationResponse result = await _authenticationService.LoginAsync(request).ConfigureAwait(false);
return Ok(result);
BaseResponse<AuthenticationResponse> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.LoginAsync(request).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}

/// <summary>
/// User password reset using reset token.
/// </summary>
/// <param name="request">New password details request</param>
[HttpPost("reset-password")]
public async Task<IActionResult> ResetPasswordAsync(PasswordResetRequest request)
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> ResetPasswordAsync(PasswordResetRequest request)
{
AuthenticationResponse result = await _authenticationService.ResetPasswordAsync(request).ConfigureAwait(false);
return Ok(result);
BaseResponse<AuthenticationResponse> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.ResetPasswordAsync(request).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}

/// <summary>
/// Forgot user password.
/// </summary>
/// <param name="email">Email of user to reset password</param>
[HttpPost("forgot-password")]
public async Task<IActionResult> ForgotPasswordAsync(string email)
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<string>>> ForgotPasswordAsync(string email)
{
string result = await _authenticationService.ForgotPasswordAsync(email).ConfigureAwait(false);
return Ok(result);
BaseResponse<string> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.ForgotPasswordAsync(email).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}
}
}
62 changes: 49 additions & 13 deletions DotnetFoundation/DotnetFoundation.Api/Controllers/UserController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using DotnetFoundation.Application.DTO.UserDTO;
using DotnetFoundation.Application.Interfaces.Services;
using DotnetFoundation.Application.Models.Common;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using DotnetFoundation.Application.Models.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -15,26 +17,60 @@ public UserController(IUserService userService)
_userService = userService;
}


/// <summary>
/// Get all user records.
/// Authorize - LEAD role
/// </summary>
[HttpGet]
[Authorize(Roles = "LEAD")]
public async Task<IActionResult> GetAllUsersAsync()
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<List<UserResponse>>>> GetAllUsersAsync()
{
List<UserResponse> result = await _userService.GetAllUsersAsync().ConfigureAwait(false);
return Ok(result);
BaseResponse<List<UserResponse>> response = new(ResponseStatus.Fail);

response.Data = await _userService.GetAllUsersAsync().ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}

/// <summary>
/// Get user by id.
/// </summary>
/// <param name="userId">Id of user record</param>
[HttpGet("{userId}")]
public async Task<IActionResult> GetUserByIdAsync(int userId)
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<UserResponse>>> GetUserByIdAsync(int userId)
{
UserResponse? result = await _userService.GetUserByIdAsync(userId).ConfigureAwait(false);
return Ok(result);
BaseResponse<UserResponse> response = new(ResponseStatus.Fail);

response.Data = await _userService.GetUserByIdAsync(userId).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}
[Authorize(Roles = "ADMIN")]

/// <summary>
/// Add new user role.
/// Authorize - ADMIN role
/// </summary>
/// <param name="roleRequest">Role request details</param>
[HttpPost("add-role")]
public async Task<IActionResult> AddUserRoleAsync(UserRoleRequest roleRequest)
[Authorize(Roles = "ADMIN")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<bool>>> AddUserRoleAsync(UserRoleRequest roleRequest)
{
bool result = await _userService.AddUserRoleAsync(roleRequest.Email, roleRequest.Role).ConfigureAwait(false);
return Ok(result);
BaseResponse<bool> response = new(ResponseStatus.Fail);

response.Data = await _userService.AddUserRoleAsync(roleRequest.Email, roleRequest.Role).ConfigureAwait(false);
response.Status = ResponseStatus.Success;

return Ok(response);
}
}
}
3 changes: 0 additions & 3 deletions DotnetFoundation/DotnetFoundation.Api/DotEnv.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;

namespace DotnetFoundation.Api;

public static class DotEnv
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotnetFoundation.Application\DotnetFoundation.Application.csproj" />
<ProjectReference Include="..\DotnetFoundation.Infrastructure\DotnetFoundation.Infrastructure.csproj" />
<ProjectReference Include="..\DotnetFoundation.Services\DotnetFoundation.Services.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using AutoMapper;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using DotnetFoundation.Domain.Entities;

namespace DotnetFoundation.Api.Helpers;

public class AutoMappingProfile : Profile
{
public AutoMappingProfile()
{
CreateMap<User, UserResponse>();
}
}
69 changes: 43 additions & 26 deletions DotnetFoundation/DotnetFoundation.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using DotnetFoundation.Api;
using DotnetFoundation.Application;
using DotnetFoundation.Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using DotnetFoundation.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
using Swashbuckle.AspNetCore.Filters;
using System.Reflection;
using System.Text.Json.Serialization;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

string root = Directory.GetCurrentDirectory();
string dotenv = Path.GetFullPath(Path.Combine(root, "..", "..", ".env"));
Expand All @@ -16,38 +20,47 @@
.AddEnvironmentVariables();

// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
builder.Services.AddControllers(options =>
{
options.Filters.Add(new ProducesAttribute("application/json"));
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

// AutoMapper Services
builder.Services.AddAutoMapper(typeof(Program));

// Swagger UI Services
builder.Services.AddSwaggerGen(options =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Your API", Version = "v1" });
options.SwaggerDoc("v1", new OpenApiInfo { Title = "DotnetFoundation API", Version = "v1" });

// Add JWT authentication
OpenApiSecurityScheme securityScheme = new OpenApiSecurityScheme
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter your JWT token",
Name = "JWT Authorization",
Description = "Standard Authorization header using the Bearer Scheme (\"bearer {token}\")",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer", // or another scheme type as needed
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};

c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, securityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ securityScheme, Array.Empty<string>() }
Type = SecuritySchemeType.ApiKey
});

options.OperationFilter<SecurityRequirementsOperationFilter>();

string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string filePath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(filePath);
});

builder.Services.AddInfrastructure(builder.Configuration);
// Adding HTTP Context
builder.Services.AddHttpContextAccessor();

// Modify builder for different layers
builder.Services.AddApplication();
builder.Services.AddServices();
builder.Services.AddInfrastructure(builder.Configuration);

// Building application
WebApplication app = builder.Build();

// Configure the HTTP request pipeline.
Expand All @@ -57,8 +70,12 @@
app.UseSwaggerUI();
}

app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();
Expand Down
6 changes: 5 additions & 1 deletion DotnetFoundation/DotnetFoundation.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"Appsettings": {
"LoginAuthTokenExpiryTimeInHrs": 2,
"IdentityTokenLifespanInMinutes": 30
},
"Logging": {
"LogLevel": {
"Default": "Information",
Expand All @@ -20,7 +24,7 @@
},
"Emails": {
"ForgetPassword": {
"Path": "../DotnetFoundation.Domain/Templates/Emails/ForgetPasswordTemplate.html",
"Path": "../DotnetFoundation.Api/wwwroot/Templates/Emails/ForgetPasswordTemplate.html",
"Subject": "Forget password"
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forget Password Token</title>
</head>
<body>
</head>
<body>
<h3>Use this token to reset your password.</h3>
<p><strong>Token :</strong> {body}</p>
</body>
</body>
</html>

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 9c29387

Please sign in to comment.