diff --git a/back/Domain/ApplicationDbContext.cs b/back/Domain/ApplicationDbContext.cs new file mode 100644 index 0000000..c2961e7 --- /dev/null +++ b/back/Domain/ApplicationDbContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; + +namespace back.Domain; + +public class ApplicationDbContext : DbContext +{ + public ApplicationDbContext(DbContextOptions options) : base(options) + { + Database.EnsureCreated(); + Database.Migrate(); + } +} \ No newline at end of file diff --git a/back/Domain/IEntity.cs b/back/Domain/IEntity.cs new file mode 100644 index 0000000..7b8164e --- /dev/null +++ b/back/Domain/IEntity.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace back.Domain; + +public interface IEntity +{ + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + string Id + { + get; + set; + } +} \ No newline at end of file diff --git a/back/Infrastructure/AutoMapperProfile.cs b/back/Infrastructure/AutoMapperProfile.cs new file mode 100644 index 0000000..7c11001 --- /dev/null +++ b/back/Infrastructure/AutoMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; + +namespace back.Infrastructure; + +public class AutoMapperProfile : Profile +{ + public AutoMapperProfile() + { + //CreateMap() + // .ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId)) + // .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName)) + // .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName)) + // .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) + // .ForMember(dest => dest.BirthYear, opt => opt.MapFrom(src => src.Birthday.Year)) + // .ForMember(dest => dest.BirthMonth, opt => opt.MapFrom(src => src.Birthday.Month)) + // .ForMember(dest => dest.BirthDay, opt => opt.MapFrom(src => src.Birthday.Day)) + // .ForMember(dest => dest.OccupationName, opt => opt.Ignore()) + + } +} diff --git a/back/Program.cs b/back/Program.cs new file mode 100644 index 0000000..ad6d266 --- /dev/null +++ b/back/Program.cs @@ -0,0 +1,150 @@ +using Autofac.Extensions.DependencyInjection; +using AutoMapper; +using back.Domain; +using back.Infrastructure; +using MCVIngenieros.Healthchecks; +using MediatR.Extensions.FluentValidation.AspNetCore; +using Microsoft.EntityFrameworkCore; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Scalar.AspNetCore; +using Serilog; + +namespace back; + +public class Program +{ + public static void Main(string[] args) + { + var configFiles = Path.Combine(AppContext.BaseDirectory, "configs"); + if (!Directory.Exists(configFiles)) + { + Directory.CreateDirectory(configFiles); + } + + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; + + var configurationBuilder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{environment}.json", optional: false, reloadOnChange: true); + + var configs = Directory.GetFiles(configFiles, "*.json", SearchOption.AllDirectories); + foreach (var config in configs) + { + configurationBuilder.AddJsonFile(config, optional: true, reloadOnChange: true); + } + + var configuration = configurationBuilder.Build(); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .MinimumLevel.Verbose() + .Enrich.FromLogContext() + .CreateLogger(); + + try + { + var builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddConfiguration(configuration); + builder.Host.UseSerilog(); + builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + + builder.Services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); + }); + + builder.Services.AddFluentValidation([typeof(Program).Assembly]); + builder.Services.AddAutoMapper(opts => + { + opts.AddProfile(); + opts.AllowNullCollections = true; + opts.AllowNullDestinationValues = true; + opts.DestinationMemberNamingConvention = new PascalCaseNamingConvention(); + }); + + builder.Services.AddHealthChecksSupport().DiscoverHealthChecks(); + builder.Services.AddLogging(); + builder.Logging.AddOpenTelemetry(options => + { + options + .SetResourceBuilder( + ResourceBuilder.CreateDefault() + .AddService(AppDomain.CurrentDomain.FriendlyName)) + .AddConsoleExporter(); + }); + builder.Services.AddOpenTelemetry() + .ConfigureResource(resource => resource.AddService(AppDomain.CurrentDomain.FriendlyName)) + .WithTracing(tracing => tracing + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()) + .WithMetrics(metrics => metrics + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()); + + builder.Services.AddDataProtection(); + + builder.Services.AddCors(options => + { + options.AddDefaultPolicy(policy => + { + policy + .WithOrigins(builder.Configuration.GetSection("AllowedHosts").Get() ?? []) + .SetIsOriginAllowedToAllowWildcardSubdomains() + .SetPreflightMaxAge(TimeSpan.FromMinutes(10)) + .AllowCredentials() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + builder.Services.AddHttpContextAccessor(); + builder.Services.AddAntiforgery(options => + { + options.HeaderName = "X-XSRF-TOKEN"; + }); + + builder.Services.AddDbContext(opts => + { + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); + opts.UseSqlite(connectionString); + }); + + builder.Services.AddControllers(options => + { + options.Filters.Add(new Microsoft.AspNetCore.Mvc.RequireHttpsAttribute()); + }); + // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi + builder.Services.AddOpenApi(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.MapOpenApi(); + app.MapScalarApiReference("/api-docs", opt => + { + opt.WithTitle("My API Documentation"); + }); + } + + app.UseHttpsRedirection(); + + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + } + catch (Exception ex) + { + Log.Fatal(ex, "Application start-up failed"); + } + finally + { + Log.CloseAndFlush(); + } + } +} diff --git a/back/Properties/launchSettings.json b/back/Properties/launchSettings.json new file mode 100644 index 0000000..acdfb46 --- /dev/null +++ b/back/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "api-docs", + "applicationUrl": "https://localhost:7157", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/back/appsettings.Development.json b/back/appsettings.Development.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/back/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/back/appsettings.Production.json b/back/appsettings.Production.json new file mode 100644 index 0000000..3b46298 --- /dev/null +++ b/back/appsettings.Production.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "mmorales.photo" +} diff --git a/back/appsettings.Staging.json b/back/appsettings.Staging.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/back/appsettings.Staging.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/back/appsettings.json b/back/appsettings.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/back/appsettings.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/back/back.csproj b/back/back.csproj new file mode 100644 index 0000000..7ea4bfc --- /dev/null +++ b/back/back.csproj @@ -0,0 +1,70 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/back/back.sln b/back/back.sln new file mode 100644 index 0000000..3b14fa6 --- /dev/null +++ b/back/back.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36401.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "back", "back.csproj", "{C78E8225-44D3-434B-AC2A-C8F4459BB18C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C78E8225-44D3-434B-AC2A-C8F4459BB18C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D5ABA005-3E91-4220-9B2C-874C0BED7E34} + EndGlobalSection +EndGlobal diff --git a/back/configs/healthchecks.json b/back/configs/healthchecks.json new file mode 100644 index 0000000..54fd253 --- /dev/null +++ b/back/configs/healthchecks.json @@ -0,0 +1,15 @@ +{ + "HealthChecksConfigs": { + "CacheDuration": "00:30:00", + "Timeout": "00:00:05", + "AssembliesToScan": [ + "back" + ] + //"MyCheck": { + // "RetryAttempts": 2, + // "Timeout": "00:05:00", + // "RetryDelay": "00:00:10", + // "Severity": "Info" + //} + } +} \ No newline at end of file diff --git a/back/configs/serilog.json b/back/configs/serilog.json new file mode 100644 index 0000000..d7e7e0b --- /dev/null +++ b/back/configs/serilog.json @@ -0,0 +1,41 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.OpenTelemetry" ], + "MinimumLevel": { + "Default": "Information" + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "File", + "Args": { + "path": "Logs/log-.txt", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "rollOnFileSizeLimit": true, + "retainedFileCountLimit": 31, + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "OpenTelemetry", + "Args": { + "endpoint": "http://localhost:4317", + "protocol": "Grpc", + "resourceAttributes": { + "service.name": "back.mmorales.photo", + "deployment.environment": "development" + } + } + } + ], + "Enrich": [ + "FromLogContext", + "WithThreadId", + "WithProcessId", + "WithEnvironmentName" + ] + } +} diff --git a/Captura de pantalla 2025-08-11 003208.png b/docs/uiux/paleta (1).png similarity index 100% rename from Captura de pantalla 2025-08-11 003208.png rename to docs/uiux/paleta (1).png diff --git a/Captura de pantalla 2025-08-11 003533.png b/docs/uiux/paleta (2).png similarity index 100% rename from Captura de pantalla 2025-08-11 003533.png rename to docs/uiux/paleta (2).png