diff --git a/Init.sh b/Init.sh new file mode 100644 index 0000000..6066a5f --- /dev/null +++ b/Init.sh @@ -0,0 +1,15 @@ +#! /bin/bash + +echo 'Assuming you have docker installed on your machine. If not, please install docker first.' + +echo 'Pulling the latest version of SQL Server 2019 on Ubuntu 16.04 from Microsoft Container Registry (MCR)' +docker pull mcr.microsoft.com/mssql/server + +echo 'Running the SQL Server 2019 container' +docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Password123456" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-latest + +echo 'SQL Server 2019 container is running' + +echo 'run the command "update-database" inside VisualStudio click Tools -> NuGet Package Manager -> Package Manager Console' +echo 'Select the "Default project" as "src\Application' + diff --git a/README.md b/README.md index e5a4530..b80dc1f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # sm-medication-api SmartMed Back End code challenge + + +Steps to run api: + +1. Clone the repository +2. Open the project in your favorite IDE (Visual Studio) +3. Open terminal/cmd and navigate to the solution folder +3. Run Init.sh with the command .\Init.sh to pull docker sql server image and run the container +4. After the sql server's running + - On VisualStudio click Tools -> NuGet Package Manager -> Package Manager Console + - Select the "Default project" as "src\Application" + - run the command dotnet ef database update --project .\src\SM.Medication.Infrastructure\SM.Medication.Infrastructure.csproj --startup-project .\src\SM.Medication.Api\SM.Medication.Api.csproj" + +Now the Solution is ready, with a base SQL Server DB running with 3 medications on the medication table already. + +PS: The token to call the API is "SmartMed eyAiVG9rZW4iOiAiMTIzIiwgIlJvbGUiOiAiQWRtaW4ifQ==" \ No newline at end of file diff --git a/src/SM.Medication.Api/EndPoints/MedicationEndPoints.cs b/src/SM.Medication.Api/EndPoints/MedicationEndPoints.cs new file mode 100644 index 0000000..eb4d93f --- /dev/null +++ b/src/SM.Medication.Api/EndPoints/MedicationEndPoints.cs @@ -0,0 +1,20 @@ +namespace SM.Medication.Api.EndPoints; + +public static class MedicationEndPoints +{ + private const string MEDICATION_TAG = "Medication"; + public static void MapMedicationEndPoints(this WebApplication app) + { + app.MapGet("/medications", + async (IMedicationHandler handler) => + { + return await handler.Handle(); + }) + .WithTags(MEDICATION_TAG) + .WithMetadata(new SwaggerOperationAttribute(MEDICATION_TAG, "Get List of Medications")) + .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status200OK, "Success!")) + .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status401Unauthorized, "You're not Authorized!")) + .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status500InternalServerError, "Failed!")) + .RequireAuthorization(); ; + } +} diff --git a/src/SM.Medication.Api/Extensions/MapsterExtensions.cs b/src/SM.Medication.Api/Extensions/MapsterExtensions.cs new file mode 100644 index 0000000..e6c3b12 --- /dev/null +++ b/src/SM.Medication.Api/Extensions/MapsterExtensions.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using Mapster; +using MapsterMapper; + +namespace SM.Medication.Api.Extensions; + +public static class MapsterExtensions +{ + public static void SetupMapster(this WebApplicationBuilder builder) + { + var typeAdapterConfig = TypeAdapterConfig.GlobalSettings; + + // Scan the current assembly + typeAdapterConfig.Scan(Assembly.GetExecutingAssembly()); + + // Scan assemblies of referenced projects + var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); + foreach (var assemblyName in referencedAssemblies) + { + var assembly = Assembly.Load(assemblyName); + typeAdapterConfig.Scan(assembly); + } + + var mapperConfig = new Mapper(typeAdapterConfig); + builder.Services.AddSingleton(mapperConfig); + } +} diff --git a/src/SM.Medication.Api/Extensions/OpenAiExtensions.cs b/src/SM.Medication.Api/Extensions/OpenAiExtensions.cs new file mode 100644 index 0000000..0f81130 --- /dev/null +++ b/src/SM.Medication.Api/Extensions/OpenAiExtensions.cs @@ -0,0 +1,48 @@ +using Microsoft.OpenApi.Models; + +namespace SM.Medication.Api.Extensions; + +public static class OpenAiExtensions +{ + public static void SetupOpenApi(this WebApplicationBuilder builder) + { + var securityScheme = new OpenApiSecurityScheme() + { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = AuthSchemeConstants.SmartMedAuthScheme, + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JSON Web Token based security" + }; + + var securityRequirement = new OpenApiSecurityRequirement(); + var secondSecurityDefinition = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = AuthSchemeConstants.SmartMedAuthScheme + } + }; + securityRequirement.Add(secondSecurityDefinition, []); + + var info = new OpenApiInfo() + { + Version = "v1", + Title = "SmartMed Medication API", + Description = "Medication API service for any communication regard medicament.", + TermsOfService = new Uri("https://www.smartmed.world"), + }; + + + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(action => + { + action.SwaggerDoc("v1", info); + action.AddSecurityDefinition(AuthSchemeConstants.SmartMedAuthScheme, securityScheme); + action.AddSecurityRequirement(securityRequirement); + action.EnableAnnotations(); + }); + } +} diff --git a/src/SM.Medication.Api/Extensions/ServiceCollectionExtensions.cs b/src/SM.Medication.Api/Extensions/ServiceCollectionExtensions.cs index 1c6b50c..e79a30f 100644 --- a/src/SM.Medication.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/SM.Medication.Api/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ -using Microsoft.OpenApi.Models; +using SM.Medication.Application.Handlers; +using SM.Medication.Domain.Interfaces; +using SM.Medication.Infrastructure.Services.Repositories; using SM.Medication.Shared.Options; -using SmartMed.Medication.Auth.Constants; namespace SM.Medication.Api.Extensions; @@ -15,45 +16,9 @@ public static void AddOptions(this WebApplicationBuilder builder) }); } - public static void SetupOpenApi(this IServiceCollection services) + public static void AddInterfaces(this WebApplicationBuilder builder) { - var securityScheme = new OpenApiSecurityScheme() - { - Name = "Authorization", - Type = SecuritySchemeType.ApiKey, - Scheme = AuthSchemeConstants.SmartMedAuthScheme, - BearerFormat = "JWT", - In = ParameterLocation.Header, - Description = "JSON Web Token based security" - }; - - var securityRequirement = new OpenApiSecurityRequirement(); - var secondSecurityDefinition = new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = AuthSchemeConstants.SmartMedAuthScheme - } - }; - securityRequirement.Add(secondSecurityDefinition, []); - - var info = new OpenApiInfo() - { - Version = "v1", - Title = "SmartMed Medication API", - Description = "Medication API service for any communication regard medicament.", - TermsOfService = new Uri("https://www.smartmed.world"), - }; - - - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(action => - { - action.SwaggerDoc("v1", info); - action.AddSecurityDefinition(AuthSchemeConstants.SmartMedAuthScheme, securityScheme); - action.AddSecurityRequirement(securityRequirement); - action.EnableAnnotations(); - }); + builder.Services.AddScoped(); + builder.Services.AddScoped(); } } diff --git a/src/SM.Medication.Api/GlobalUsings.cs b/src/SM.Medication.Api/GlobalUsings.cs new file mode 100644 index 0000000..aeef450 --- /dev/null +++ b/src/SM.Medication.Api/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using SM.Medication.Application.Interfaces; +global using SmartMed.Medication.Auth.Constants; +global using Swashbuckle.AspNetCore.Annotations; diff --git a/src/SM.Medication.Api/Program.cs b/src/SM.Medication.Api/Program.cs index 3c650a6..5e79d50 100644 --- a/src/SM.Medication.Api/Program.cs +++ b/src/SM.Medication.Api/Program.cs @@ -1,20 +1,17 @@ -using Microsoft.AspNetCore.Localization; -using System.Globalization; using Microsoft.EntityFrameworkCore; +using SM.Medication.Api.EndPoints; using SM.Medication.Api.Extensions; using SM.Medication.Auth; using SM.Medication.Auth.Extensions; using SM.Medication.Auth.Options; using SM.Medication.Infrastructure.Persistence; -using SmartMed.Medication.Auth.Constants; -using Swashbuckle.AspNetCore.Annotations; var builder = WebApplication.CreateBuilder(args); builder.AddOptions(); builder.Services.AddCors(options => options.AddPolicy("allowAny", o => o.AllowAnyOrigin())); -builder.Services.SetupOpenApi(); +builder.SetupOpenApi(); builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("MedicationDb"))); @@ -25,35 +22,11 @@ builder.Services.AddAuthorization(); -var app = builder.Build(); - -//var supportedCultures = new[] -//{ -// new CultureInfo("en-US"), -// new CultureInfo("fr"), -//}; -//app.UseRequestLocalization(new RequestLocalizationOptions -//{ -// DefaultRequestCulture = new RequestCulture("en-US"), -// // Formatting numbers, dates, etc. -// SupportedCultures = supportedCultures, -// // UI strings that we have localized. -// SupportedUICultures = supportedCultures -//}); - -app.MapGet("/", () => "Hello World!"); - +builder.AddInterfaces(); +builder.SetupMapster(); -app.MapGet("/medications", async (SmartMedMedicationDbContext dbContext) => -{ - var medications = await dbContext.Medications.ToListAsync(); - return medications; -}).WithTags("Home") - .WithMetadata(new SwaggerOperationAttribute("Home", "Base Get Endpoint")) - .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status200OK, "Success!")) - .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status401Unauthorized, "You're not Authorized!")) - .WithMetadata(new SwaggerResponseAttribute(StatusCodes.Status500InternalServerError, "Failed!")) - .RequireAuthorization(); ; +var app = builder.Build(); +app.MapMedicationEndPoints(); if (app.Environment.IsDevelopment()) { diff --git a/src/SM.Medication.Application/GlobalUsings.cs b/src/SM.Medication.Application/GlobalUsings.cs new file mode 100644 index 0000000..d292cb7 --- /dev/null +++ b/src/SM.Medication.Application/GlobalUsings.cs @@ -0,0 +1 @@ +global using SM.Medication.Domain.DTO; diff --git a/src/SM.Medication.Application/Handlers/MedicationHandler.cs b/src/SM.Medication.Application/Handlers/MedicationHandler.cs new file mode 100644 index 0000000..193671b --- /dev/null +++ b/src/SM.Medication.Application/Handlers/MedicationHandler.cs @@ -0,0 +1,16 @@ +using MapsterMapper; +using SM.Medication.Application.Interfaces; +using SM.Medication.Domain.Interfaces; + +namespace SM.Medication.Application.Handlers; + +public class MedicationHandler( + IMedicationRepository medicationRepository, + IMapper mapper) : IMedicationHandler +{ + public async Task> Handle() + { + var medications = await medicationRepository.GetAll(); + return mapper.Map>(medications); + } +} diff --git a/src/SM.Medication.Application/Interfaces/IMedicationHandler.cs b/src/SM.Medication.Application/Interfaces/IMedicationHandler.cs new file mode 100644 index 0000000..19147ae --- /dev/null +++ b/src/SM.Medication.Application/Interfaces/IMedicationHandler.cs @@ -0,0 +1,5 @@ +namespace SM.Medication.Application.Interfaces; +public interface IMedicationHandler +{ + Task> Handle(); +} diff --git a/src/SM.Medication.Application/SM.Medication.Application.csproj b/src/SM.Medication.Application/SM.Medication.Application.csproj index 08065c7..ad04729 100644 --- a/src/SM.Medication.Application/SM.Medication.Application.csproj +++ b/src/SM.Medication.Application/SM.Medication.Application.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/src/SM.Medication.Auth/Extensions/SmartMedAuthExtensions.cs b/src/SM.Medication.Auth/Extensions/SmartMedAuthExtensions.cs index 84e0798..4b9ef6f 100644 --- a/src/SM.Medication.Auth/Extensions/SmartMedAuthExtensions.cs +++ b/src/SM.Medication.Auth/Extensions/SmartMedAuthExtensions.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Http; namespace SM.Medication.Auth.Extensions; public static class SmartMedAuthExtensions diff --git a/src/SM.Medication.Auth/GlobalUsings.cs b/src/SM.Medication.Auth/GlobalUsings.cs new file mode 100644 index 0000000..91d9799 --- /dev/null +++ b/src/SM.Medication.Auth/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Microsoft.AspNetCore.Authentication; +global using Microsoft.AspNetCore.Http; diff --git a/src/SM.Medication.Auth/Options/SMAuthSchemeOptions.cs b/src/SM.Medication.Auth/Options/SMAuthSchemeOptions.cs index e153faa..72bd8ac 100644 --- a/src/SM.Medication.Auth/Options/SMAuthSchemeOptions.cs +++ b/src/SM.Medication.Auth/Options/SMAuthSchemeOptions.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Authentication; - namespace SM.Medication.Auth.Options; public class SMAuthSchemeOptions : AuthenticationSchemeOptions { diff --git a/src/SM.Medication.Auth/SmartMedAuthenticationHandler.cs b/src/SM.Medication.Auth/SmartMedAuthenticationHandler.cs index 4127858..ae4300d 100644 --- a/src/SM.Medication.Auth/SmartMedAuthenticationHandler.cs +++ b/src/SM.Medication.Auth/SmartMedAuthenticationHandler.cs @@ -3,8 +3,6 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; diff --git a/src/SM.Medication.Domain/Common/AuditableEntity.cs b/src/SM.Medication.Domain/Common/AuditableEntity.cs index c1d6e10..8c22144 100644 --- a/src/SM.Medication.Domain/Common/AuditableEntity.cs +++ b/src/SM.Medication.Domain/Common/AuditableEntity.cs @@ -1,6 +1,5 @@ -using Microsoft.AspNetCore.Http; - namespace SM.Medication.Domain.Common; + public class AuditableEntity { public string? CreatedBy { get; set; } diff --git a/src/SM.Medication.Domain/DTO/MedicationDTO.cs b/src/SM.Medication.Domain/DTO/MedicationDTO.cs new file mode 100644 index 0000000..2bae48f --- /dev/null +++ b/src/SM.Medication.Domain/DTO/MedicationDTO.cs @@ -0,0 +1,7 @@ +namespace SM.Medication.Domain.DTO; + +public class MedicationDTO +{ + public string? Name { get; set; } + public int Quantity { get; set; } +} diff --git a/src/SM.Medication.Domain/Interfaces/IMedicationRepository.cs b/src/SM.Medication.Domain/Interfaces/IMedicationRepository.cs new file mode 100644 index 0000000..4148aff --- /dev/null +++ b/src/SM.Medication.Domain/Interfaces/IMedicationRepository.cs @@ -0,0 +1,6 @@ +namespace SM.Medication.Domain.Interfaces; + +public interface IMedicationRepository +{ + Task> GetAll(); +} diff --git a/src/SM.Medication.Infrastructure/GlobalUsings.cs b/src/SM.Medication.Infrastructure/GlobalUsings.cs new file mode 100644 index 0000000..832e011 --- /dev/null +++ b/src/SM.Medication.Infrastructure/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/src/SM.Medication.Infrastructure/Mapping/MedicationMapping.cs b/src/SM.Medication.Infrastructure/Mapping/MedicationMapping.cs index f1efa32..0ec6117 100644 --- a/src/SM.Medication.Infrastructure/Mapping/MedicationMapping.cs +++ b/src/SM.Medication.Infrastructure/Mapping/MedicationMapping.cs @@ -1,4 +1,3 @@ -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace SM.Medication.Infrastructure.Mapping; diff --git a/src/SM.Medication.Infrastructure/Migrations/20240726082831_InitialDataBase.cs b/src/SM.Medication.Infrastructure/Migrations/20240726082831_InitialDataBase.cs index 3141624..7b679bc 100644 --- a/src/SM.Medication.Infrastructure/Migrations/20240726082831_InitialDataBase.cs +++ b/src/SM.Medication.Infrastructure/Migrations/20240726082831_InitialDataBase.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/SM.Medication.Infrastructure/Persistence/SmartMedMedicationDbContext.cs b/src/SM.Medication.Infrastructure/Persistence/SmartMedMedicationDbContext.cs index dad6143..abdaa8e 100644 --- a/src/SM.Medication.Infrastructure/Persistence/SmartMedMedicationDbContext.cs +++ b/src/SM.Medication.Infrastructure/Persistence/SmartMedMedicationDbContext.cs @@ -1,5 +1,3 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; namespace SM.Medication.Infrastructure.Persistence; diff --git a/src/SM.Medication.Infrastructure/Services/Repositories/MedicationRepository.cs b/src/SM.Medication.Infrastructure/Services/Repositories/MedicationRepository.cs new file mode 100644 index 0000000..d7f50b5 --- /dev/null +++ b/src/SM.Medication.Infrastructure/Services/Repositories/MedicationRepository.cs @@ -0,0 +1,14 @@ +using SM.Medication.Domain.Interfaces; +using SM.Medication.Infrastructure.Persistence; + +namespace SM.Medication.Infrastructure.Services.Repositories; + +public class MedicationRepository(SmartMedMedicationDbContext context) : IMedicationRepository +{ + public async Task> GetAll() + { + return await context + .Medications + .ToListAsync(); + } +}