Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update main #7

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,5 @@ MigrationBackup/

# Fody - auto-generated XML schema
FodyWeavers.xsd
.vscode/
.vscode/
.idea/
5 changes: 2 additions & 3 deletions UrlShortener.sln
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UrlShortener.Api", "src\UrlShortener.Api\UrlShortener.Api.csproj", "{E08DE300-8CBA-401B-A034-9274F1FFAB84}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UrlShortener.Application", "src\UrlShortener.Application\UrlShortener.Application.csproj", "{B8A8776E-CD45-4934-9FEF-8B4230ABF758}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UrlShortener.Domain", "src\UrlShortener.Domain\UrlShortener.Domain.csproj", "{AB8F30E4-35FE-4628-8EF7-406C11410837}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UrlShortener.Entity", "src\UrlShortener.Entity\UrlShortener.Entity.csproj", "{AB8F30E4-35FE-4628-8EF7-406C11410837}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UrlShortener.Data", "src\UrlShortener.Data\UrlShortener.Data.csproj", "{1557D2F0-E80D-4D94-9638-C2905CB44D2C}"
EndProject
Expand Down
21 changes: 11 additions & 10 deletions src/UrlShortener.Api/Attributes/ApiKeyAuthorizeAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Primitives;
using UrlShortener.Api.Models;
using UrlShortener.Api.Utility;
using UrlShortener.Application.Common.Constants;

namespace UrlShortener.Api.Attributes;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ApiKeyAuthorizeAttribute : Attribute, IAuthorizationFilter
{
public string Message { get; set; } = StatusCodeErrorMessage.ApiKeyErrorMessage;

public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue(ApiKeyOptions.ApiKeyHeaderName, out StringValues apiKeyFromHeaders) == false)
if (context.HttpContext.Request.Headers.TryGetValue(ApiKeyOptions.ApiKeyHeaderName,
out var apiKeyFromHeaders) == false)
{
context.Result = new UnauthorizedObjectResult(
new ApiErrors(StatusCodes.Status401Unauthorized, "ApiKey is required", null));
new ApiErrors(StatusCodes.Status401Unauthorized, Message));
return;
}

IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
string apiKeyFromSettings = configuration.GetValue<string>(ApiKeyOptions.ApiKeySectionValue)
?? throw new ArgumentNullException("ApiKey is null", nameof(ApiKeyOptions.ApiKeySectionValue));
var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKeyFromSettings = configuration.GetValue<string>(ApiKeyOptions.ApiKeySectionValue)
?? throw new ArgumentNullException(nameof(ApiKeyOptions.ApiKeySectionValue));

if (apiKeyFromHeaders.Equals(apiKeyFromSettings) == false)
{
context.Result = new UnauthorizedObjectResult(
new ApiErrors(StatusCodes.Status401Unauthorized, "ApiKey is required", null));
return;
new ApiErrors(StatusCodes.Status401Unauthorized, Message));
}
}
}

}
25 changes: 25 additions & 0 deletions src/UrlShortener.Api/ConfigureServices/ConfigureAppDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using UrlShortener.Application.Interfaces;
using UrlShortener.Data;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureAppDbContext
{
public static void AddAppDbContext(this IServiceCollection services, IConfiguration configuration)
{
bool inMemory = configuration.GetValue<bool>("InMemory");
if (inMemory)
{
services.AddDbContext<AppDbContext>(options => { options.UseInMemoryDatabase("test"); });
}
else
{
string connectionString = configuration["ConnectionStrings:PostgresSQL"]
?? throw new ArgumentNullException(nameof(configuration));
services.AddDbContext<AppDbContext>(options => { options.UseNpgsql(connectionString); });
}

services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using UrlShortener.Application.Interfaces;
using UrlShortener.Application.Services;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureApplicationServices
{
public static void AddApplicationServices(this IServiceCollection services)
{
services.AddTransient<IAliasGenerator, AliasGenerator>();
services.AddTransient<ILinkService, LinkService>();
services.AddSingleton<ISystemDateTime>(new SystemDateTime());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using UrlShortener.Api.Authentication;
using UrlShortener.Api.Models;
using UrlShortener.Application.Common.Constants;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureAuthenticationService
{
public static void AddAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtOptions jwtOptions = new();
configuration.GetSection(JwtOptions.ConfigKey).Bind(jwtOptions);
services.AddAuthentication(options =>
{
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
};
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
context.HandleResponse();
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "application/json";
var err = new ApiErrors(StatusCodes.Status401Unauthorized,
StatusCodeErrorMessage.JwtTokenErrorMessage);
var jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
await context.Response.WriteAsJsonAsync(err, jsonSerializerOptions);
}
};
});
}
}
12 changes: 12 additions & 0 deletions src/UrlShortener.Api/ConfigureServices/ConfigureFluentValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentValidation;
using UrlShortener.Application;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureFluentValidator
{
public static void AddFluentValidationService(this IServiceCollection services)
{
services.AddValidatorsFromAssembly(ApplicationAssembly.Assembly);
}
}
18 changes: 18 additions & 0 deletions src/UrlShortener.Api/ConfigureServices/ConfigureMediatR.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using MediatR;
using UrlShortener.Application;
using UrlShortener.Application.Behaviors;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureMediatR
{
public static void AddMediatRServices(this IServiceCollection services)
{
services.AddMediatR(config =>
{
config.RegisterServicesFromAssembly(ApplicationAssembly.Assembly);
});
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
}
}
18 changes: 18 additions & 0 deletions src/UrlShortener.Api/ConfigureServices/ConfigureSerilog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Serilog;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureSerilog
{
public static void AddSerilog(this WebApplicationBuilder builder)
{
builder.Host.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(builder.Configuration)
#if DEBUG
.WriteTo.Seq("http://localhost:5341")
#endif
.Enrich.FromLogContext();
});
}
}
63 changes: 63 additions & 0 deletions src/UrlShortener.Api/ConfigureServices/ConfigureSwagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Microsoft.OpenApi.Models;

namespace UrlShortener.Api.ConfigureServices;

public static class ConfigureSwagger
{
public static void AddSwagger(this IServiceCollection services)
{
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "UrlShortener Api", Version = "v1" });
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = "ApiKey authorization",
Type = SecuritySchemeType.ApiKey,
Name = "x-api-key",
In = ParameterLocation.Header,
Scheme = "ApiKeyScheme"
});
var apiKey = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
},
In = ParameterLocation.Header
};
var apiKEyRequirement = new OpenApiSecurityRequirement
{
{ apiKey, new List<string>() }
};
options.AddSecurityRequirement(apiKEyRequirement);

options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Scheme = "Bearer",
BearerFormat = "JWT"
});
var bearer = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
In = ParameterLocation.Header
};
var bearerRequirement = new OpenApiSecurityRequirement
{
{ bearer, new List<string>() }
};
options.AddSecurityRequirement(bearerRequirement);
});
}

}
22 changes: 10 additions & 12 deletions src/UrlShortener.Api/Controllers/ApiControllerBase.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;

namespace UrlShortener.Api.Controllers
{
[ApiController]
public abstract class ApiControllerBase : ControllerBase
{
private readonly IMediator _mediator;
namespace UrlShortener.Api.Controllers;

protected ApiControllerBase(IMediator mediator)
{
_mediator = mediator;
}
public abstract class ApiControllerBase : ControllerBase
{
private readonly IMediator _mediator;

protected IMediator Mediator => _mediator ?? HttpContext.RequestServices.GetService<IMediator>()!;
protected ApiControllerBase(IMediator mediator)
{
_mediator = mediator;
}
}

protected IMediator Mediator => _mediator ?? HttpContext.RequestServices.GetService<IMediator>()!;
}
Loading
Loading