diff --git a/app/server/APIGateway/src/APIGateway/DependenciesInjection.cs b/app/server/APIGateway/src/APIGateway/DependenciesInjection.cs index c8ac829e6..80e9918a6 100644 --- a/app/server/APIGateway/src/APIGateway/DependenciesInjection.cs +++ b/app/server/APIGateway/src/APIGateway/DependenciesInjection.cs @@ -139,7 +139,12 @@ public static WebApplication UseAPIServices(this WebApplication app) private static bool Authorize(HttpContext ctx) { - if (ctx.Items.DownstreamRoute().AuthenticationOptions.AuthenticationProviderKey == null) return true; + if (ctx.Items.DownstreamRoute() + .AuthenticationOptions + .AuthenticationProviderKeys?.Length == 0) + { + return true; + } else { bool auth = false; diff --git a/app/server/Contract/Contract/Event/UserEvent/UpdateUserTotalRecipeEvent.cs b/app/server/Contract/Contract/Event/UserEvent/UpdateUserTotalRecipeEvent.cs index f2e05697d..e52a858ac 100644 --- a/app/server/Contract/Contract/Event/UserEvent/UpdateUserTotalRecipeEvent.cs +++ b/app/server/Contract/Contract/Event/UserEvent/UpdateUserTotalRecipeEvent.cs @@ -1,5 +1,4 @@ using MassTransit; -using MassTransit; namespace Contract.Event.UserEvent; [EntityName("UpdateUserTotalRecipeEvent")] public class UpdateUserTotalRecipeEvent diff --git a/app/server/Contract/Contract/Protos/TrackingService.proto b/app/server/Contract/Contract/Protos/TrackingService.proto index c3c01b405..984ac1139 100644 --- a/app/server/Contract/Contract/Protos/TrackingService.proto +++ b/app/server/Contract/Contract/Protos/TrackingService.proto @@ -1,7 +1,6 @@ syntax = "proto3"; import "google/protobuf/timestamp.proto"; -import "google/protobuf/wrappers.proto"; package TrackingProto; diff --git a/app/server/Contract/Contract/Utilities/EnvUtility.cs b/app/server/Contract/Contract/Utilities/EnvUtility.cs index fd12041ab..aa7ddbd8b 100644 --- a/app/server/Contract/Contract/Utilities/EnvUtility.cs +++ b/app/server/Contract/Contract/Utilities/EnvUtility.cs @@ -55,6 +55,10 @@ public static string GetConnectionString() var user = DotNetEnv.Env.GetString("POSTGRES_USER", "Not found").Trim(); var pwd = DotNetEnv.Env.GetString("POSTGRES_PASSWORD", "Not found").Trim(); var connectionString = $"Host={host};Port={port};Database={db};Username={user};Password={pwd};"; + if (IsProduction()) + { + connectionString = $"{connectionString}SSL Mode=Require;Trust Server Certificate=true;"; + } return connectionString; } diff --git a/app/server/IdentityService/src/DuendeIdentityServer/AppCommandHandler.cs b/app/server/IdentityService/src/DuendeIdentityServer/AppCommandHandler.cs index 31ed0458e..9376daf1e 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/AppCommandHandler.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/AppCommandHandler.cs @@ -1,4 +1,6 @@ using Contract.Constants; +using Microsoft.EntityFrameworkCore; +using Duende.IdentityServer.EntityFramework.DbContexts; using IdentityService.Domain.Interfaces; using IdentityService.Infrastructure; @@ -29,9 +31,10 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder { case COMMAND_ARGS.MIGRATE: dbContext.MigrateDb(sp); + sp.GetRequiredService().Database.Migrate(); break; case COMMAND_ARGS.SEED: - dbContext.SeedDb(sp).GetAwaiter().GetResult(); + await dbContext.SeedDb(sp); break; } } diff --git a/app/server/IdentityService/src/DuendeIdentityServer/DTOs/IdentityProfile.cs b/app/server/IdentityService/src/DuendeIdentityServer/DTOs/IdentityProfile.cs new file mode 100644 index 000000000..795c7adbb --- /dev/null +++ b/app/server/IdentityService/src/DuendeIdentityServer/DTOs/IdentityProfile.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using Contract.DTOs.UserDTO; +using IdentityService.Application.Account.Commands; + +namespace DuendeIdentityServer.DTOs; + +public class IdentityProfile : Profile +{ + public IdentityProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} diff --git a/app/server/IdentityService/src/DuendeIdentityServer/DTOs/MappingConfig.cs b/app/server/IdentityService/src/DuendeIdentityServer/DTOs/MappingConfig.cs deleted file mode 100755 index 0678ad730..000000000 --- a/app/server/IdentityService/src/DuendeIdentityServer/DTOs/MappingConfig.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AutoMapper; -using Contract.DTOs.UserDTO; -using IdentityService.Application.Account.Commands; - -namespace DuendeIdentityServer.DTOs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - }); - - - return mappingConfig; - } -} \ No newline at end of file diff --git a/app/server/IdentityService/src/DuendeIdentityServer/DuendeIdentityServer.csproj b/app/server/IdentityService/src/DuendeIdentityServer/DuendeIdentityServer.csproj index 37077aff8..27cc8e3fa 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/DuendeIdentityServer.csproj +++ b/app/server/IdentityService/src/DuendeIdentityServer/DuendeIdentityServer.csproj @@ -15,16 +15,12 @@ - - - - @@ -34,7 +30,11 @@ - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Extensions/ReinforcedTypingsExtension.cs b/app/server/IdentityService/src/DuendeIdentityServer/Extensions/ReinforcedTypingsExtension.cs index abdf7da66..9f05a0d72 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Extensions/ReinforcedTypingsExtension.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Extensions/ReinforcedTypingsExtension.cs @@ -1,5 +1,6 @@ using Contract.Extension; using IdentityService.Domain.Errors; +using Reinforced.Typings.Ast.TypeNames; using Reinforced.Typings.Fluent; using ConfigurationBuilder = Reinforced.Typings.Fluent.ConfigurationBuilder; namespace DuendeIdentityServer.Extensions; @@ -20,6 +21,10 @@ public static void ConfigureReinforcedTypings(ConfigurationBuilder builder) builder.ConfigCommonReinforcedTypings(EXPORT_FILE_PATH, FILE_NAME, errorsTypes); + builder.Substitute(typeof(DateTimeOffset), new RtSimpleTypeName("string")); + builder.Substitute(typeof(Microsoft.AspNetCore.Identity.IdentityRole), new RtSimpleTypeName("any")); + builder.Substitute(typeof(IFormFile), new RtSimpleTypeName("any")); + // DTO and Entities builder.ExportAsInterfaces([ typeof(StatisticEntity), diff --git a/app/server/IdentityService/src/DuendeIdentityServer/HostingExtensions.cs b/app/server/IdentityService/src/DuendeIdentityServer/HostingExtensions.cs index 0e3a97c38..04906f9ee 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/HostingExtensions.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/HostingExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace DuendeIdentityServer; @@ -43,9 +44,12 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde }); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddCommonAPIWithoutAuthServices(); services @@ -68,6 +72,20 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde // keep old key for 7 days in discovery for validation of tokens options.KeyManagement.RetentionDuration = TimeSpan.FromDays(7); }) + .AddOperationalStore(options => + { + options.ConfigureDbContext = builder => + builder.UseNpgsql(EnvUtility.GetConnectionString(), + options => options.MigrationsAssembly("IdentityService.Infrastructure") + .EnableRetryOnFailure( + maxRetryCount: 10, + maxRetryDelay: TimeSpan.FromSeconds(15), + errorCodesToAdd: null + )); + + options.EnableTokenCleanup = true; + options.TokenCleanupInterval = 3600; + }) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) @@ -123,7 +141,7 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde return builder.Build(); } - public static async Task ConfigurePipelineAsync(this WebApplication app) + public static Task ConfigurePipeline(this WebApplication app) { if (EnvUtility.IsProduction() || EnvUtility.IsStaging()) { @@ -184,11 +202,11 @@ public static async Task ConfigurePipelineAsync(this WebApplicat app.UseSignalRServiceAsync(); app.Use(async (context, next) => - { - Console.WriteLine($"RemoteIp: {context.Connection.RemoteIpAddress}"); - await next(); - }); + { + Console.WriteLine($"RemoteIp: {context.Connection.RemoteIpAddress}"); + await next(); + }); - return app; + return Task.FromResult(app); } } diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePassword/Index.cshtml.cs b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePassword/Index.cshtml.cs index a37045359..58cac3e16 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePassword/Index.cshtml.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePassword/Index.cshtml.cs @@ -42,7 +42,7 @@ public Index( _sender = sender; } - public async Task OnGet(string returnUrl, string identifier) + public Task OnGet(string returnUrl, string identifier) { Input = new InputModel { @@ -53,7 +53,7 @@ public async Task OnGet(string returnUrl, string identifier) ViewData["ReturnUrl"] = encodedRedirectUri; - return Page(); + return Task.FromResult(Page()); } public async Task OnPost() diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePasswordSuccess/Index.cshtml.cs b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePasswordSuccess/Index.cshtml.cs index 060108bd0..93ddca79e 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePasswordSuccess/Index.cshtml.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ChangePasswordSuccess/Index.cshtml.cs @@ -11,11 +11,11 @@ public class Index : PageModel public Index() { } - public async Task OnGet(string returnUrl) + public Task OnGet(string returnUrl) { var encodedRedirectUri = WebUtility.UrlEncode(returnUrl); ViewData["ReturnUrl"] = encodedRedirectUri; - return Page(); + return Task.FromResult(Page()); } } diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ForgotPassword/Index.cshtml.cs b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ForgotPassword/Index.cshtml.cs index 54bb71c34..e0e3f4bb1 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ForgotPassword/Index.cshtml.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/ForgotPassword/Index.cshtml.cs @@ -128,7 +128,7 @@ public async Task OnPost() } catch (Exception ex) { - ModelState.AddModelError(string.Empty, "Send OTP failed! Please try again"); + ModelState.AddModelError(string.Empty, $"Send OTP failed! Please try again ({ex.Message})"); } break; case "ReturnFind": diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/VerifyForgotPassword/Index.cshtml.cs b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/VerifyForgotPassword/Index.cshtml.cs index f0193cfe4..2e527c008 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/VerifyForgotPassword/Index.cshtml.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Pages/Account/VerifyForgotPassword/Index.cshtml.cs @@ -30,7 +30,7 @@ public Index( _sender = sender; } - public async Task OnGet(string returnUrl, string identifier) + public Task OnGet(string returnUrl, string identifier) { Input = new InputModel { @@ -45,7 +45,7 @@ public async Task OnGet(string returnUrl, string identifier) IsValidOTP = false, }; - return Page(); + return Task.FromResult(Page()); } public async Task OnPost() @@ -176,7 +176,7 @@ public async Task OnPost() Identifier = Input.Identifier, OTP = Input.OTP!, Method = IdentifierUtility.Check(Input.Identifier), - Password = Input.Password + Password = Input.Password! }); result.ThrowIfFailure(); diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Pages/ExternalLogin/Callback.cshtml.cs b/app/server/IdentityService/src/DuendeIdentityServer/Pages/ExternalLogin/Callback.cshtml.cs index 0216cba23..f84be3ba6 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Pages/ExternalLogin/Callback.cshtml.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Pages/ExternalLogin/Callback.cshtml.cs @@ -71,7 +71,7 @@ public async Task OnGet() externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? throw new InvalidOperationException("Unknown userid"); - var provider = result.Properties.Items["scheme"] ?? throw new InvalidOperationException("Null scheme in authentiation properties"); + var provider = result.Properties?.Items["scheme"] ?? throw new InvalidOperationException("Null scheme in authentiation properties"); var providerUserId = userIdClaim.Value; // find external user @@ -87,7 +87,7 @@ public async Task OnGet() Provider = provider, ProviderUserId = providerUserId, Claims = externalUser.Claims, - AccessToken = accessToken + AccessToken = accessToken! }); response.ThrowIfFailure(); user = response.Value; diff --git a/app/server/IdentityService/src/DuendeIdentityServer/Program.cs b/app/server/IdentityService/src/DuendeIdentityServer/Program.cs index 15ddaf33c..5257dd1e0 100755 --- a/app/server/IdentityService/src/DuendeIdentityServer/Program.cs +++ b/app/server/IdentityService/src/DuendeIdentityServer/Program.cs @@ -13,7 +13,7 @@ var app = await builder .ConfigureServices() - .ConfigurePipelineAsync(); + .ConfigurePipeline(); app.Start(); diff --git a/app/server/IdentityService/src/IdentityService.Application/Configs/IdentityProfile.cs b/app/server/IdentityService/src/IdentityService.Application/Configs/IdentityProfile.cs new file mode 100644 index 000000000..180893106 --- /dev/null +++ b/app/server/IdentityService/src/IdentityService.Application/Configs/IdentityProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Google.Protobuf.Collections; +using IdentityService.Application.Configs.MapperConverters; + +namespace IdentityService.Application.Configs; + +public class IdentityProfile : Profile +{ + public IdentityProfile() + { + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + } +} diff --git a/app/server/IdentityService/src/IdentityService.Application/Configs/MappingConfig.cs b/app/server/IdentityService/src/IdentityService.Application/Configs/MappingConfig.cs deleted file mode 100644 index 96093944a..000000000 --- a/app/server/IdentityService/src/IdentityService.Application/Configs/MappingConfig.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Google.Protobuf.Collections; -using IdentityService.Application.Configs.MapperConverters; - -namespace IdentityService.Application.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - }); - - - return mappingConfig; - } -} diff --git a/app/server/IdentityService/src/IdentityService.Application/DependencyInjection.cs b/app/server/IdentityService/src/IdentityService.Application/DependencyInjection.cs index 471443eb7..cf05e8450 100644 --- a/app/server/IdentityService/src/IdentityService.Application/DependencyInjection.cs +++ b/app/server/IdentityService/src/IdentityService.Application/DependencyInjection.cs @@ -15,9 +15,12 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); services.AddGrpcClientServices(); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); return services; } diff --git a/app/server/IdentityService/src/IdentityService.Application/IdentityService.Application.csproj b/app/server/IdentityService/src/IdentityService.Application/IdentityService.Application.csproj index c962e5247..a362a9904 100644 --- a/app/server/IdentityService/src/IdentityService.Application/IdentityService.Application.csproj +++ b/app/server/IdentityService/src/IdentityService.Application/IdentityService.Application.csproj @@ -7,7 +7,7 @@ - + diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/DependencyInjection.cs b/app/server/IdentityService/src/IdentityService.Infrastructure/DependencyInjection.cs index 8fac6d386..7d266453c 100644 --- a/app/server/IdentityService/src/IdentityService.Infrastructure/DependencyInjection.cs +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/DependencyInjection.cs @@ -27,9 +27,23 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti public static IServiceCollection AddMinimalInfrastructureServices(this IServiceCollection services) { + var connectionString = Contract.Utilities.EnvUtility.GetConnectionString(); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); services.AddDbContext(options => - options.UseNpgsql(Contract.Utilities.EnvUtility.GetConnectionString())); + options.UseNpgsql(connectionString)); + services.AddIdentityServer() + .AddOperationalStore(options => + { + options.ConfigureDbContext = builder => + builder.UseNpgsql(connectionString, + options => options.MigrationsAssembly("IdentityService.Infrastructure") + .EnableRetryOnFailure( + maxRetryCount: 10, + maxRetryDelay: TimeSpan.FromSeconds(15), + errorCodesToAdd: null + )); + + }); services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/IdentityService.Infrastructure.csproj b/app/server/IdentityService/src/IdentityService.Infrastructure/IdentityService.Infrastructure.csproj index 0f0d6f71f..63b595dd4 100644 --- a/app/server/IdentityService/src/IdentityService.Infrastructure/IdentityService.Infrastructure.csproj +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/IdentityService.Infrastructure.csproj @@ -8,6 +8,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/ApplicationDbContext.cs b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/ApplicationDbContext.cs index e2c55ee08..d9f26fa95 100644 --- a/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/ApplicationDbContext.cs @@ -26,8 +26,7 @@ public ApplicationDbContext(DbContextOptions options) public void MigrateDb(IServiceProvider serviceProvider) { - var db = serviceProvider.GetRequiredService(); - db.Database.Migrate(); + serviceProvider.GetRequiredService().Database.Migrate(); Console.WriteLine("✅ Database migrated successfully."); } diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.Designer.cs b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.Designer.cs new file mode 100644 index 000000000..e60bbb15d --- /dev/null +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.Designer.cs @@ -0,0 +1,270 @@ +// +using System; +using Duende.IdentityServer.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IdentityService.Infrastructure.Persistence.IdentityServer.PersistedGrant +{ + [DbContext(typeof(PersistedGrantDbContext))] + [Migration("20260611045123_InitialIdentityServerPersistedGrantDbMigration")] + partial class InitialIdentityServerPersistedGrantDbMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("timestamp without time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Algorithm") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("DataProtected") + .HasColumnType("boolean"); + + b.Property("IsX509Certificate") + .HasColumnType("boolean"); + + b.Property("Use") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Use"); + + b.ToTable("Keys", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ConsumedTime"); + + b.HasIndex("Expiration"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PushedAuthorizationRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAtUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("Parameters") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReferenceValueHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAtUtc"); + + b.HasIndex("ReferenceValueHash") + .IsUnique(); + + b.ToTable("PushedAuthorizationRequests", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.ServerSideSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisplayName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Expires") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Renewed") + .HasColumnType("timestamp without time zone"); + + b.Property("Scheme") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("DisplayName"); + + b.HasIndex("Expires"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SessionId"); + + b.HasIndex("SubjectId"); + + b.ToTable("ServerSideSessions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.cs b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.cs new file mode 100644 index 000000000..6f23ca045 --- /dev/null +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/20260611045123_InitialIdentityServerPersistedGrantDbMigration.cs @@ -0,0 +1,209 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IdentityService.Infrastructure.Persistence.IdentityServer.PersistedGrant +{ + /// + public partial class InitialIdentityServerPersistedGrantDbMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DeviceCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "timestamp without time zone", nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: false), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "Keys", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Version = table.Column(type: "integer", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Use = table.Column(type: "text", nullable: true), + Algorithm = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + IsX509Certificate = table.Column(type: "boolean", nullable: false), + DataProtected = table.Column(type: "boolean", nullable: false), + Data = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Keys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Key = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Type = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "timestamp without time zone", nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + ConsumedTime = table.Column(type: "timestamp without time zone", nullable: true), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PushedAuthorizationRequests", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ReferenceValueHash = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + ExpiresAtUtc = table.Column(type: "timestamp without time zone", nullable: false), + Parameters = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PushedAuthorizationRequests", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ServerSideSessions", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Key = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Scheme = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + SubjectId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + DisplayName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Renewed = table.Column(type: "timestamp without time zone", nullable: false), + Expires = table.Column(type: "timestamp without time zone", nullable: true), + Data = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerSideSessions", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_Keys_Use", + table: "Keys", + column: "Use"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_ConsumedTime", + table: "PersistedGrants", + column: "ConsumedTime"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Key", + table: "PersistedGrants", + column: "Key", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_SessionId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "SessionId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_PushedAuthorizationRequests_ExpiresAtUtc", + table: "PushedAuthorizationRequests", + column: "ExpiresAtUtc"); + + migrationBuilder.CreateIndex( + name: "IX_PushedAuthorizationRequests_ReferenceValueHash", + table: "PushedAuthorizationRequests", + column: "ReferenceValueHash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ServerSideSessions_DisplayName", + table: "ServerSideSessions", + column: "DisplayName"); + + migrationBuilder.CreateIndex( + name: "IX_ServerSideSessions_Expires", + table: "ServerSideSessions", + column: "Expires"); + + migrationBuilder.CreateIndex( + name: "IX_ServerSideSessions_Key", + table: "ServerSideSessions", + column: "Key", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ServerSideSessions_SessionId", + table: "ServerSideSessions", + column: "SessionId"); + + migrationBuilder.CreateIndex( + name: "IX_ServerSideSessions_SubjectId", + table: "ServerSideSessions", + column: "SubjectId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "Keys"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + + migrationBuilder.DropTable( + name: "PushedAuthorizationRequests"); + + migrationBuilder.DropTable( + name: "ServerSideSessions"); + } + } +} diff --git a/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/PersistedGrantDbContextModelSnapshot.cs b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/PersistedGrantDbContextModelSnapshot.cs new file mode 100644 index 000000000..fc686109b --- /dev/null +++ b/app/server/IdentityService/src/IdentityService.Infrastructure/Persistence/IdentityServer/PersistedGrant/PersistedGrantDbContextModelSnapshot.cs @@ -0,0 +1,267 @@ +// +using System; +using Duende.IdentityServer.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IdentityService.Infrastructure.Persistence.IdentityServer.PersistedGrant +{ + [DbContext(typeof(PersistedGrantDbContext))] + partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("timestamp without time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Algorithm") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("DataProtected") + .HasColumnType("boolean"); + + b.Property("IsX509Certificate") + .HasColumnType("boolean"); + + b.Property("Use") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Use"); + + b.ToTable("Keys", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedTime") + .HasColumnType("timestamp without time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ConsumedTime"); + + b.HasIndex("Expiration"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PushedAuthorizationRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExpiresAtUtc") + .HasColumnType("timestamp without time zone"); + + b.Property("Parameters") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReferenceValueHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAtUtc"); + + b.HasIndex("ReferenceValueHash") + .IsUnique(); + + b.ToTable("PushedAuthorizationRequests", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.ServerSideSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisplayName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Expires") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Renewed") + .HasColumnType("timestamp without time zone"); + + b.Property("Scheme") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("DisplayName"); + + b.HasIndex("Expires"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SessionId"); + + b.HasIndex("SubjectId"); + + b.ToTable("ServerSideSessions", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/app/server/NotificationService/src/EmailWorker/EmailWorker.csproj b/app/server/NotificationService/src/EmailWorker/EmailWorker.csproj index 4728ffc44..b5d80bd61 100644 --- a/app/server/NotificationService/src/EmailWorker/EmailWorker.csproj +++ b/app/server/NotificationService/src/EmailWorker/EmailWorker.csproj @@ -16,7 +16,7 @@ - + diff --git a/app/server/NotificationService/src/NotificationService.API/AppCommandHandler.cs b/app/server/NotificationService/src/NotificationService.API/AppCommandHandler.cs index 7ed170701..e5f13cf80 100644 --- a/app/server/NotificationService/src/NotificationService.API/AppCommandHandler.cs +++ b/app/server/NotificationService/src/NotificationService.API/AppCommandHandler.cs @@ -6,7 +6,7 @@ namespace NotificationService.API; public static class AppCommandHandler { - public static async Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) + public static Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) { var uniqueArgs = args .Where(COMMAND_ARGS.All.Contains) @@ -14,7 +14,7 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder .ToArray(); if (uniqueArgs.Length == 0) { - return false; + return Task.FromResult(false); } builder.Services.AddMinimalInfrastructureServices(); @@ -31,6 +31,6 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder break; } } - return true; + return Task.FromResult(true); } } \ No newline at end of file diff --git a/app/server/NotificationService/src/NotificationService.Application/Configs/MappingConfig.cs b/app/server/NotificationService/src/NotificationService.Application/Configs/MappingConfig.cs deleted file mode 100644 index 3cec212b1..000000000 --- a/app/server/NotificationService/src/NotificationService.Application/Configs/MappingConfig.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AutoMapper; -using Contract.DTOs.UserDTO; -using Google.Protobuf.Collections; -using NotificationService.Application.Configs.MapperConverters; -using UserProto; - -namespace NotificationService.Application.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - // Grpc mapping - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - - config.CreateMap() - .ForMember(dest => dest.Users, - opt => opt.MapFrom(src => src.Users.ToDictionary( - user => user.Key, - user => new CommonProto.GrpcSimpleUser - { - AccountId = user.Value.AccountId.ToString(), - AvtUrl = user.Value.AvtUrl, - DisplayName = user.Value.DisplayName - }))).ReverseMap(); - }); - //mappingConfig.AssertConfigurationIsValid(); - - return mappingConfig; - } -} diff --git a/app/server/NotificationService/src/NotificationService.Application/Configs/NotificationProfile.cs b/app/server/NotificationService/src/NotificationService.Application/Configs/NotificationProfile.cs new file mode 100644 index 000000000..5f4239196 --- /dev/null +++ b/app/server/NotificationService/src/NotificationService.Application/Configs/NotificationProfile.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using Contract.DTOs.UserDTO; +using Google.Protobuf.Collections; +using NotificationService.Application.Configs.MapperConverters; +using UserProto; + +namespace NotificationService.Application.Configs; + +public class NotificationProfile : Profile +{ + public NotificationProfile() + { + // Grpc mapping + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + + CreateMap() + .ForMember(dest => dest.Users, + opt => opt.MapFrom(src => src.Users.ToDictionary( + user => user.Key, + user => new CommonProto.GrpcSimpleUser + { + AccountId = user.Value.AccountId.ToString(), + AvtUrl = user.Value.AvtUrl, + DisplayName = user.Value.DisplayName + }))).ReverseMap(); + } +} \ No newline at end of file diff --git a/app/server/NotificationService/src/NotificationService.Application/DependencyInjection.cs b/app/server/NotificationService/src/NotificationService.Application/DependencyInjection.cs index 5195aec17..ed754816e 100644 --- a/app/server/NotificationService/src/NotificationService.Application/DependencyInjection.cs +++ b/app/server/NotificationService/src/NotificationService.Application/DependencyInjection.cs @@ -11,9 +11,12 @@ public static class DependencyInjection public static IServiceCollection AddApplicationServices(this IServiceCollection services) { // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); services.AddGrpcClientService(); return services; diff --git a/app/server/NotificationService/src/NotificationService.Application/NotificationService.Application.csproj b/app/server/NotificationService/src/NotificationService.Application/NotificationService.Application.csproj index b83087536..1bb113f8c 100644 --- a/app/server/NotificationService/src/NotificationService.Application/NotificationService.Application.csproj +++ b/app/server/NotificationService/src/NotificationService.Application/NotificationService.Application.csproj @@ -6,7 +6,7 @@ enable - + diff --git a/app/server/RecipeService/src/RecipeService.API/AppCommandHandler.cs b/app/server/RecipeService/src/RecipeService.API/AppCommandHandler.cs index 569757cd9..13a96a729 100644 --- a/app/server/RecipeService/src/RecipeService.API/AppCommandHandler.cs +++ b/app/server/RecipeService/src/RecipeService.API/AppCommandHandler.cs @@ -6,7 +6,7 @@ namespace RecipeService.API; public static class AppCommandHandler { - public static async Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) + public static Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) { var uniqueArgs = args .Where(COMMAND_ARGS.All.Contains) @@ -14,7 +14,7 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder .ToArray(); if (uniqueArgs.Length == 0) { - return false; + return Task.FromResult(false); } builder.Services.AddMinimalInfrastructureServices(); @@ -31,6 +31,6 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder break; } } - return true; + return Task.FromResult(true); } } \ No newline at end of file diff --git a/app/server/RecipeService/src/RecipeService.API/Configs/MappingConfig.cs b/app/server/RecipeService/src/RecipeService.API/Configs/MappingConfig.cs deleted file mode 100644 index 2f1b5f70f..000000000 --- a/app/server/RecipeService/src/RecipeService.API/Configs/MappingConfig.cs +++ /dev/null @@ -1,97 +0,0 @@ -using AutoMapper; -using Contract.DTOs.RecipeDTO; -using RecipeProto; -using RecipeService.API.DTOs; -using RecipeService.Application.Recipes.Commands; -using RecipeService.Domain.Entities; - -namespace RecipeService.API.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - - config.CreateMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); - - config.CreateMap().ReverseMap(); - - config.CreateMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) - .ForMember(dest => dest.CookTime, opt => opt.MapFrom(src => src.CookTime)) - .ReverseMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) - .ForMember(dest => dest.CookTime, opt => - opt.MapFrom(src => !string.IsNullOrEmpty(src.CookTime) ? src.CookTime : "")); - - config.CreateMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); - - config.CreateMap().ReverseMap(); - - config.CreateMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); - - //Grpc mapping - config.CreateMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) - .ForMember(dest => dest.CookTime, opt => opt.MapFrom(src => src.CookTime)) - .ReverseMap() - .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) - .ForMember(dest => dest.CookTime, opt => - opt.MapFrom(src => !string.IsNullOrEmpty(src.CookTime) ? src.CookTime : "")); - - config.CreateMap().ReverseMap(); - - config.CreateMap() - .ForMember( - dest => dest.Status, - opt => opt.MapFrom(src => Enum.Parse(src.Status)) - ) - .ForMember( - dest => dest.Category, - opt => opt.MapFrom(src => Enum.Parse(src.Category)) - ) - .ReverseMap() - .ForMember( - dest => dest.Status, - opt => opt.MapFrom(src => src.Status.ToString()) - ) - .ForMember( - dest => dest.Category, - opt => opt.MapFrom(src => src.Category.ToString()) - ); - - ///////////////////////////////////////////////////// - - - config.CreateMap().ReverseMap(); - - config.CreateMap() - .ForMember( - dest => dest.Status, - opt => opt.MapFrom(src => Enum.Parse(src.Status)) - ) - .ForMember( - dest => dest.Category, - opt => opt.MapFrom(src => Enum.Parse(src.Category)) - ) - .ReverseMap() - .ForMember( - dest => dest.Status, - opt => opt.MapFrom(src => src.Status.ToString()) - ) - .ForMember( - dest => dest.Category, - opt => opt.MapFrom(src => src.Category.ToString()) - ); - - - }); - - return mappingConfig; - } -} diff --git a/app/server/RecipeService/src/RecipeService.API/Configs/RecipeProfile.cs b/app/server/RecipeService/src/RecipeService.API/Configs/RecipeProfile.cs new file mode 100644 index 000000000..fddf2177b --- /dev/null +++ b/app/server/RecipeService/src/RecipeService.API/Configs/RecipeProfile.cs @@ -0,0 +1,89 @@ +using AutoMapper; +using Contract.DTOs.RecipeDTO; +using RecipeProto; +using RecipeService.API.DTOs; +using RecipeService.Application.Recipes.Commands; +using RecipeService.Domain.Entities; + +namespace RecipeService.API.Configs; + +public class RecipeProfile : Profile +{ + public RecipeProfile() + { + CreateMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); + + CreateMap().ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) + .ForMember(dest => dest.CookTime, opt => opt.MapFrom(src => src.CookTime)) + .ReverseMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) + .ForMember(dest => dest.CookTime, opt => + opt.MapFrom(src => !string.IsNullOrEmpty(src.CookTime) ? src.CookTime : "")); + + CreateMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); + + CreateMap().ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)).ReverseMap(); + + //Grpc mapping + CreateMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) + .ForMember(dest => dest.CookTime, opt => opt.MapFrom(src => src.CookTime)) + .ReverseMap() + .ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.Steps)) + .ForMember(dest => dest.CookTime, opt => + opt.MapFrom(src => !string.IsNullOrEmpty(src.CookTime) ? src.CookTime : "")); + + CreateMap().ReverseMap(); + + CreateMap() + .ForMember( + dest => dest.Status, + opt => opt.MapFrom(src => Enum.Parse(src.Status)) + ) + .ForMember( + dest => dest.Category, + opt => opt.MapFrom(src => Enum.Parse(src.Category)) + ) + .ReverseMap() + .ForMember( + dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString()) + ) + .ForMember( + dest => dest.Category, + opt => opt.MapFrom(src => src.Category.ToString()) + ); + + ///////////////////////////////////////////////////// + + + CreateMap().ReverseMap(); + + CreateMap() + .ForMember( + dest => dest.Status, + opt => opt.MapFrom(src => Enum.Parse(src.Status)) + ) + .ForMember( + dest => dest.Category, + opt => opt.MapFrom(src => Enum.Parse(src.Category)) + ) + .ReverseMap() + .ForMember( + dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString()) + ) + .ForMember( + dest => dest.Category, + opt => opt.MapFrom(src => src.Category.ToString()) + ); + } +} \ No newline at end of file diff --git a/app/server/RecipeService/src/RecipeService.API/DependenciesInjection.cs b/app/server/RecipeService/src/RecipeService.API/DependenciesInjection.cs index 21eefa4b5..f09668052 100644 --- a/app/server/RecipeService/src/RecipeService.API/DependenciesInjection.cs +++ b/app/server/RecipeService/src/RecipeService.API/DependenciesInjection.cs @@ -24,9 +24,12 @@ public static WebApplicationBuilder AddAPIServices(this WebApplicationBuilder bu services.AddSwaggerServices(); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddCommonAPIServices(); diff --git a/app/server/RecipeService/src/RecipeService.API/Extensions/ReinforcedTypingsExtension.cs b/app/server/RecipeService/src/RecipeService.API/Extensions/ReinforcedTypingsExtension.cs index a6eb9172d..e19796051 100644 --- a/app/server/RecipeService/src/RecipeService.API/Extensions/ReinforcedTypingsExtension.cs +++ b/app/server/RecipeService/src/RecipeService.API/Extensions/ReinforcedTypingsExtension.cs @@ -4,6 +4,7 @@ using RecipeService.Domain.Entities; using RecipeService.Domain.Errors; using RecipeService.Domain.Responses; +using Reinforced.Typings.Ast.TypeNames; using Reinforced.Typings.Fluent; using ConfigurationBuilder = Reinforced.Typings.Fluent.ConfigurationBuilder; namespace RecipeService.API.Extensions; @@ -26,6 +27,8 @@ public static void ConfigureReinforcedTypings(ConfigurationBuilder builder) Directory.CreateDirectory(EXPORT_FILE_PATH); builder.ConfigCommonReinforcedTypings(EXPORT_FILE_PATH, FILE_NAME, errorsTypes); + builder.Substitute(typeof(IFormFile), new RtSimpleTypeName("any")); + builder.Substitute(typeof(TagValue), new RtSimpleTypeName("any")); // DTO and Entites builder.ExportAsInterfaces([ typeof(DateStatisticEntity), diff --git a/app/server/RecipeService/src/RecipeService.Application/Configs/MappingConfig.cs b/app/server/RecipeService/src/RecipeService.Application/Configs/MappingConfig.cs deleted file mode 100644 index 2dcf82666..000000000 --- a/app/server/RecipeService/src/RecipeService.Application/Configs/MappingConfig.cs +++ /dev/null @@ -1,56 +0,0 @@ -using AutoMapper; -using Contract.DTOs.UserDTO; -using Google.Protobuf.Collections; -using RecipeProto; -using RecipeService.Application.Configs.MapperConverters; -using RecipeService.Domain.Entities; -using RecipeService.Domain.Responses; -using UserProto; - -namespace RecipeService.Application.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - config.CreateMap() - .ForMember(dest => dest.DisplayName, opt => opt.Ignore()) - .ForMember(dest => dest.AvatarUrl, opt => opt.Ignore()) - .ReverseMap(); - - config.CreateMap() - .ForMember(dest => dest.ImageUrl, opt => opt.MapFrom(src => src.RecipeImgUrl)) - .ReverseMap(); - - config.CreateMap() - .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status.ToString())) - .ReverseMap() - .ForMember(dest => dest.Status, opt => opt.MapFrom(src => Enum.Parse(typeof(TagStatus), src.Status))); - - // Grpc mapping - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - - config.CreateMap() - .ForMember(dest => dest.Users, - opt => opt.MapFrom(src => src.Users.ToDictionary( - user => user.Key, - user => new CommonProto.GrpcSimpleUser - { - AccountId = user.Value.AccountId.ToString(), - AvtUrl = user.Value.AvtUrl, - DisplayName = user.Value.DisplayName - }))).ReverseMap(); - - config.CreateMap() - .ReverseMap(); - - }); - //mappingConfig.AssertConfigurationIsValid(); - - - return mappingConfig; - } -} diff --git a/app/server/RecipeService/src/RecipeService.Application/Configs/RecipeProfile.cs b/app/server/RecipeService/src/RecipeService.Application/Configs/RecipeProfile.cs new file mode 100644 index 000000000..0ba25dc25 --- /dev/null +++ b/app/server/RecipeService/src/RecipeService.Application/Configs/RecipeProfile.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using Contract.DTOs.UserDTO; +using Google.Protobuf.Collections; +using RecipeProto; +using RecipeService.Application.Configs.MapperConverters; +using RecipeService.Domain.Entities; +using RecipeService.Domain.Responses; +using UserProto; + +namespace RecipeService.Application.Configs; + +public class RecipeProfile : Profile +{ + public RecipeProfile() + { + CreateMap() + .ForMember(dest => dest.DisplayName, opt => opt.Ignore()) + .ForMember(dest => dest.AvatarUrl, opt => opt.Ignore()) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.ImageUrl, opt => opt.MapFrom(src => src.RecipeImgUrl)) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status.ToString())) + .ReverseMap() + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => Enum.Parse(typeof(TagStatus), src.Status))); + + // Grpc mapping + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + + CreateMap() + .ForMember(dest => dest.Users, + opt => opt.MapFrom(src => src.Users.ToDictionary( + user => user.Key, + user => new CommonProto.GrpcSimpleUser + { + AccountId = user.Value.AccountId.ToString(), + AvtUrl = user.Value.AvtUrl, + DisplayName = user.Value.DisplayName + }))).ReverseMap(); + + CreateMap() + .ReverseMap(); + } +} \ No newline at end of file diff --git a/app/server/RecipeService/src/RecipeService.Application/DependencyInjection.cs b/app/server/RecipeService/src/RecipeService.Application/DependencyInjection.cs index 0aa6600e6..fd30e6f5d 100644 --- a/app/server/RecipeService/src/RecipeService.Application/DependencyInjection.cs +++ b/app/server/RecipeService/src/RecipeService.Application/DependencyInjection.cs @@ -18,9 +18,12 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection { services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); //Grpc services.AddGrpcClientService(); diff --git a/app/server/RecipeService/src/RecipeService.Application/RecipeService.Application.csproj b/app/server/RecipeService/src/RecipeService.Application/RecipeService.Application.csproj index c94400eb5..4b7be187b 100644 --- a/app/server/RecipeService/src/RecipeService.Application/RecipeService.Application.csproj +++ b/app/server/RecipeService/src/RecipeService.Application/RecipeService.Application.csproj @@ -6,7 +6,7 @@ - + diff --git a/app/server/RecipeService/src/RecipeService.Application/ReportReasons/Queries/GetReportReasonsQueryHandler.cs b/app/server/RecipeService/src/RecipeService.Application/ReportReasons/Queries/GetReportReasonsQueryHandler.cs index 039b38939..a100b6ba9 100644 --- a/app/server/RecipeService/src/RecipeService.Application/ReportReasons/Queries/GetReportReasonsQueryHandler.cs +++ b/app/server/RecipeService/src/RecipeService.Application/ReportReasons/Queries/GetReportReasonsQueryHandler.cs @@ -11,14 +11,14 @@ public class GetReportReasonsQuery : IRequest? public class GetReportReasonsQueryHandler : IRequestHandler?>> { - public async Task?>> Handle(GetReportReasonsQuery request, CancellationToken cancellationToken) + public Task?>> Handle(GetReportReasonsQuery request, CancellationToken cancellationToken) { var lang = request.Language; var type = request.ReportType; if (string.IsNullOrEmpty(lang) || string.IsNullOrEmpty(type)) { - return Result?>.Failure(RecipeError.NullParameter); + return Task.FromResult(Result?>.Failure(RecipeError.NullParameter)); } if (type == "Recipe") @@ -29,10 +29,10 @@ public class GetReportReasonsQueryHandler : IRequestHandler?>.Failure(RecipeError.NotFound, "Not found report recipe reason"); + return Task.FromResult(Result?>.Failure(RecipeError.NotFound, "Not found report recipe reason")); } - return Result?>.Success(reasons); + return Task.FromResult(Result?>.Success(reasons)); } if (type == "Comment") @@ -43,12 +43,12 @@ public class GetReportReasonsQueryHandler : IRequestHandler?>.Failure(RecipeError.NotFound, "Not found report comment reason"); + return Task.FromResult(Result?>.Failure(RecipeError.NotFound, "Not found report comment reason")); } - return Result?>.Success(reasons); + return Task.FromResult(Result?>.Success(reasons)); } - return Result?>.Failure(RecipeError.NotFound); + return Task.FromResult(Result?>.Failure(RecipeError.NotFound)); } } diff --git a/app/server/RecipeService/src/RecipeWorker/Worker.cs b/app/server/RecipeService/src/RecipeWorker/Worker.cs index a077553be..b3baf885c 100644 --- a/app/server/RecipeService/src/RecipeWorker/Worker.cs +++ b/app/server/RecipeService/src/RecipeWorker/Worker.cs @@ -16,7 +16,7 @@ public Task StartingAsync(CancellationToken cancellationToken) _logger.LogInformation("Recipe worker starting"); return Task.CompletedTask; } - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Recipe worker start"); try @@ -26,6 +26,7 @@ public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogError(ex.Message); } + return Task.CompletedTask; } public Task StartedAsync(CancellationToken cancellationToken) { @@ -37,9 +38,10 @@ public Task StoppingAsync(CancellationToken cancellationToken) _logger.LogInformation("Recipe worker stopping"); return Task.CompletedTask; } - public async Task StopAsync(CancellationToken cancellationToken) + public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Recipe worker stop"); + return Task.CompletedTask; } public Task StoppedAsync(CancellationToken cancellationToken) { diff --git a/app/server/TrackingService/src/TrackingService.API/AppCommandHandler.cs b/app/server/TrackingService/src/TrackingService.API/AppCommandHandler.cs index dfd6ef937..f38225074 100644 --- a/app/server/TrackingService/src/TrackingService.API/AppCommandHandler.cs +++ b/app/server/TrackingService/src/TrackingService.API/AppCommandHandler.cs @@ -6,7 +6,7 @@ namespace TrackingService.API; public static class AppCommandHandler { - public static async Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) + public static Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) { var uniqueArgs = args .Where(COMMAND_ARGS.All.Contains) @@ -14,7 +14,7 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder .ToArray(); if (uniqueArgs.Length == 0) { - return false; + return Task.FromResult(false); } builder.Services.AddMinimalInfrastructureServices(); @@ -31,6 +31,6 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder break; } } - return true; + return Task.FromResult(true); } } \ No newline at end of file diff --git a/app/server/TrackingService/src/TrackingService.API/Configs/MappingConfig.cs b/app/server/TrackingService/src/TrackingService.API/Configs/MappingConfig.cs deleted file mode 100644 index 1d1411895..000000000 --- a/app/server/TrackingService/src/TrackingService.API/Configs/MappingConfig.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; - -namespace TrackingService.API.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - - - }); - - return mappingConfig; - } -} diff --git a/app/server/TrackingService/src/TrackingService.API/Controllers/TrackingController.cs b/app/server/TrackingService/src/TrackingService.API/Controllers/TrackingController.cs index b0a6420a1..97e45a080 100644 --- a/app/server/TrackingService/src/TrackingService.API/Controllers/TrackingController.cs +++ b/app/server/TrackingService/src/TrackingService.API/Controllers/TrackingController.cs @@ -34,7 +34,7 @@ public async Task GetUserViewRecipeDetailHistory([FromBody] GetUs Skip = getUserViewRecipeDetailHistoryDTO.Skip, }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } [HttpPost("search-user-view-recipe-detail-history")] @@ -52,7 +52,7 @@ public async Task SearchUserViewRecipeDetailHistory([FromBody] Se Keyword = searchUserViewRecipeDetailHistoryDTO.Keyword }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } [HttpGet("get-user-search-recipe-history")] @@ -68,7 +68,7 @@ public async Task GetUserSearchRecipe() AccountId = Guid.Parse(subjectId!), }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } [HttpGet("get-user-search-user-history")] @@ -84,7 +84,7 @@ public async Task GetUserSearchUser() AccountId = Guid.Parse(subjectId!), }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } [HttpPost("delete-user-search-user-history")] @@ -101,7 +101,7 @@ public async Task DeleteUserSearchUser([FromBody] DeleteUserSearc Keyword = deleteUserSearchUserKeywordDTO.Keyword, }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } [HttpPost("delete-user-search-recipe-history")] @@ -118,6 +118,6 @@ public async Task DeleteUserSearchRecipe([FromBody] DeleteUserSea Keyword = deleteUserSearchRecipeKeywordDTO.Keyword, }); result.ThrowIfFailure(); - return Ok(result); + return Ok(result.Value); } } diff --git a/app/server/TrackingService/src/TrackingService.API/DependenciesInjection.cs b/app/server/TrackingService/src/TrackingService.API/DependenciesInjection.cs index 3cb00df2e..72124f74d 100644 --- a/app/server/TrackingService/src/TrackingService.API/DependenciesInjection.cs +++ b/app/server/TrackingService/src/TrackingService.API/DependenciesInjection.cs @@ -1,6 +1,4 @@ -using AutoMapper; -using TrackingService.API.Configs; -using TrackingService.Infrastructure; +using TrackingService.Infrastructure; using TrackingService.Application; using Contract.Utilities; using TrackingService.API.Extensions; @@ -24,9 +22,12 @@ public static WebApplicationBuilder AddAPIServices(this WebApplicationBuilder bu services.AddSwaggerServices(); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddCommonAPIServices(); diff --git a/app/server/TrackingService/src/TrackingService.Application/Configs/MappingConfig.cs b/app/server/TrackingService/src/TrackingService.Application/Configs/MappingConfig.cs deleted file mode 100644 index 031c0c443..000000000 --- a/app/server/TrackingService/src/TrackingService.Application/Configs/MappingConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -using AutoMapper; -using Google.Protobuf.Collections; -using TrackingService.Application.Configs.MapperConverters; -using TrackingService.Domain.Responses; - -namespace TrackingService.Application.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - //Grpc - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - }); - - - - return mappingConfig; - } -} diff --git a/app/server/TrackingService/src/TrackingService.Application/Configs/TrackingProfile.cs b/app/server/TrackingService/src/TrackingService.Application/Configs/TrackingProfile.cs new file mode 100644 index 000000000..fa0d59139 --- /dev/null +++ b/app/server/TrackingService/src/TrackingService.Application/Configs/TrackingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Google.Protobuf.Collections; +using TrackingService.Application.Configs.MapperConverters; + +namespace TrackingService.Application.Configs; + +public class TrackingProfile : Profile +{ + public TrackingProfile() + { + //Grpc + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + } +} \ No newline at end of file diff --git a/app/server/TrackingService/src/TrackingService.Application/DependencyInjection.cs b/app/server/TrackingService/src/TrackingService.Application/DependencyInjection.cs index 806223296..4491fec9b 100644 --- a/app/server/TrackingService/src/TrackingService.Application/DependencyInjection.cs +++ b/app/server/TrackingService/src/TrackingService.Application/DependencyInjection.cs @@ -15,9 +15,12 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection { services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); //Grpc services.AddGrpcClientService(); diff --git a/app/server/TrackingService/src/TrackingService.Application/TrackingService.Application.csproj b/app/server/TrackingService/src/TrackingService.Application/TrackingService.Application.csproj index 144bd9568..aef2cb90d 100644 --- a/app/server/TrackingService/src/TrackingService.Application/TrackingService.Application.csproj +++ b/app/server/TrackingService/src/TrackingService.Application/TrackingService.Application.csproj @@ -6,7 +6,7 @@ enable - + diff --git a/app/server/TrackingService/src/TrackingService.Application/UserViewRecipeDetails/Queries/SearchUserViewRecipeDetaiQueryHandler.cs b/app/server/TrackingService/src/TrackingService.Application/UserViewRecipeDetails/Queries/SearchUserViewRecipeDetaiQueryHandler.cs index 51064b691..43816cfa6 100644 --- a/app/server/TrackingService/src/TrackingService.Application/UserViewRecipeDetails/Queries/SearchUserViewRecipeDetaiQueryHandler.cs +++ b/app/server/TrackingService/src/TrackingService.Application/UserViewRecipeDetails/Queries/SearchUserViewRecipeDetaiQueryHandler.cs @@ -1,5 +1,6 @@ using AutoMapper; using Google.Protobuf.Collections; +using Microsoft.EntityFrameworkCore; using RecipeProto; using TrackingService.Domain.Entities; using TrackingService.Domain.Errors; @@ -43,8 +44,12 @@ public SearchUserViewRecipeDetaiQueryHandler(GrpcRecipe.GrpcRecipeClient grpcRec } var viewsQuery = _context.UserViewRecipeDetails.Where(v => v.AccountId == accountId).OrderByDescending(v => v.UpdatedAt).AsQueryable(); - var views = viewsQuery.ToHashSet(); - var viewsMap = viewsQuery.ToDictionary(v => v.RecipeId.ToString()); + var views = await viewsQuery.ToListAsync(cancellationToken); + var viewsMap = views + .GroupBy(v => v.RecipeId) + .ToDictionary( + g => g.Key.ToString(), + g => g.OrderByDescending(x => x.UpdatedAt).First()); if (views == null || views.Count == 0) { diff --git a/app/server/UploadFileService/src/UploadFileService.API/Configs/MappingConfig.cs b/app/server/UploadFileService/src/UploadFileService.API/Configs/MappingConfig.cs deleted file mode 100644 index 72fd237d9..000000000 --- a/app/server/UploadFileService/src/UploadFileService.API/Configs/MappingConfig.cs +++ /dev/null @@ -1,35 +0,0 @@ -using AutoMapper; -using Contract.DTOs.UploadFileDTO; -using Google.Protobuf; -using Google.Protobuf.Collections; -using UploadFileProto; -using UploadFileService.API.Configs.MapperConverters; -using UploadFileService.Domain.Responses; - -namespace UploadFileService.API.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - config.CreateMap().ReverseMap(); - // Grpc mapping - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - - config.CreateMap() - .ForMember(dest => dest.Stream, otp => otp - .MapFrom(src => src.Stream.ToByteArray())) - .ReverseMap() - .ForMember(dest => dest.Stream, otp => otp - .MapFrom(src => ByteString.CopyFrom(src.Stream))); - - config.CreateMap().ReverseMap(); - }); - //mappingConfig.AssertConfigurationIsValid(); - - return mappingConfig; - } -} diff --git a/app/server/UploadFileService/src/UploadFileService.API/Configs/UploadProfile.cs b/app/server/UploadFileService/src/UploadFileService.API/Configs/UploadProfile.cs new file mode 100644 index 000000000..d88381370 --- /dev/null +++ b/app/server/UploadFileService/src/UploadFileService.API/Configs/UploadProfile.cs @@ -0,0 +1,29 @@ +using AutoMapper; +using Contract.DTOs.UploadFileDTO; +using Google.Protobuf; +using Google.Protobuf.Collections; +using UploadFileProto; +using UploadFileService.API.Configs.MapperConverters; +using UploadFileService.Domain.Responses; + +namespace UploadFileService.API.Configs; + +public class UploadProfile : Profile +{ + public UploadProfile() + { + CreateMap().ReverseMap(); + // Grpc mapping + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + + CreateMap() + .ForMember(dest => dest.Stream, otp => otp + .MapFrom(src => src.Stream.ToByteArray())) + .ReverseMap() + .ForMember(dest => dest.Stream, otp => otp + .MapFrom(src => ByteString.CopyFrom(src.Stream))); + + CreateMap().ReverseMap(); + } +} diff --git a/app/server/UploadFileService/src/UploadFileService.API/DependenciesInjection.cs b/app/server/UploadFileService/src/UploadFileService.API/DependenciesInjection.cs index 826027a36..71c7023da 100644 --- a/app/server/UploadFileService/src/UploadFileService.API/DependenciesInjection.cs +++ b/app/server/UploadFileService/src/UploadFileService.API/DependenciesInjection.cs @@ -24,9 +24,12 @@ public static WebApplicationBuilder AddAPIServices(this WebApplicationBuilder bu services.AddSwaggerServices(); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddCommonAPIServices(); diff --git a/app/server/UploadFileService/src/UploadFileService.API/UploadFileService.API.csproj b/app/server/UploadFileService/src/UploadFileService.API/UploadFileService.API.csproj index 7b095d1a6..497447adb 100644 --- a/app/server/UploadFileService/src/UploadFileService.API/UploadFileService.API.csproj +++ b/app/server/UploadFileService/src/UploadFileService.API/UploadFileService.API.csproj @@ -16,7 +16,7 @@ - + diff --git a/app/server/UserService/src/UserService.API/AppCommandHandler.cs b/app/server/UserService/src/UserService.API/AppCommandHandler.cs index 8b99b2ee4..f6c178a4b 100644 --- a/app/server/UserService/src/UserService.API/AppCommandHandler.cs +++ b/app/server/UserService/src/UserService.API/AppCommandHandler.cs @@ -6,7 +6,7 @@ namespace UserService.API; public static class AppCommandHandler { - public static async Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) + public static Task TryHandleAsync(this WebApplicationBuilder builder, string[] args) { var uniqueArgs = args .Where(COMMAND_ARGS.All.Contains) @@ -14,7 +14,7 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder .ToArray(); if (uniqueArgs.Length == 0) { - return false; + return Task.FromResult(false); } builder.Services.AddMinimalInfrastructureServices(); @@ -34,6 +34,6 @@ public static async Task TryHandleAsync(this WebApplicationBuilder builder break; } } - return true; + return Task.FromResult(true); } } \ No newline at end of file diff --git a/app/server/UserService/src/UserService.API/Configs/MappingConfig.cs b/app/server/UserService/src/UserService.API/Configs/UserProfile.cs similarity index 70% rename from app/server/UserService/src/UserService.API/Configs/MappingConfig.cs rename to app/server/UserService/src/UserService.API/Configs/UserProfile.cs index 27aac019c..2d7ce92c6 100644 --- a/app/server/UserService/src/UserService.API/Configs/MappingConfig.cs +++ b/app/server/UserService/src/UserService.API/Configs/UserProfile.cs @@ -7,19 +7,13 @@ namespace UserService.API.Configs; -public class MappingConfig +public class UserProfile : Profile { - - public static MapperConfiguration RegisterMaps() + public UserProfile() { - var mappingConfig = new MapperConfiguration(config => - { - //config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - - // Map object to grpc object - config.CreateMap() + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap() .ForMember(dest => dest.Dob, opt => opt.MapFrom(src => src.Dob.HasValue ? Timestamp.FromDateTime(((DateTime)src.Dob).ToUniversalTime()) @@ -31,9 +25,7 @@ public static MapperConfiguration RegisterMaps() opt => opt.MapFrom(src => src.Dob.ToDateTime())) .ForMember(dest => dest.AccountId, opt => opt.MapFrom(src => Guid.Parse(src.AccountId))); - - - config.CreateMap() + CreateMap() .ForMember(dest => dest.Users, opt => opt.MapFrom(src => src.Users.ToDictionary( user => user.Key, @@ -43,9 +35,5 @@ public static MapperConfiguration RegisterMaps() AvtUrl = user.Value.AvtUrl, DisplayName = user.Value.DisplayName }))).ReverseMap(); - }); - - - return mappingConfig; } } diff --git a/app/server/UserService/src/UserService.API/DependenciesInjection.cs b/app/server/UserService/src/UserService.API/DependenciesInjection.cs index ca3689566..170f56649 100644 --- a/app/server/UserService/src/UserService.API/DependenciesInjection.cs +++ b/app/server/UserService/src/UserService.API/DependenciesInjection.cs @@ -19,8 +19,12 @@ public static WebApplicationBuilder AddAPIServices(this WebApplicationBuilder bu builder.ConfigureCommonAPIServices(); - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddInfrastructureServices(); services.AddApplicationServices(); diff --git a/app/server/UserService/src/UserService.API/Extensions/ReinforcedTypingsExtension.cs b/app/server/UserService/src/UserService.API/Extensions/ReinforcedTypingsExtension.cs index d784414a2..d084300bc 100644 --- a/app/server/UserService/src/UserService.API/Extensions/ReinforcedTypingsExtension.cs +++ b/app/server/UserService/src/UserService.API/Extensions/ReinforcedTypingsExtension.cs @@ -1,6 +1,7 @@ using Contract.DTOs; using Contract.Extension; using RecipeService.API.DTOs; +using Reinforced.Typings.Ast.TypeNames; using Reinforced.Typings.Fluent; using UserService.API.DTOs; using UserService.Domain.Entities; @@ -26,6 +27,7 @@ public static void ConfigureReinforcedTypings(ConfigurationBuilder builder) Directory.CreateDirectory(EXPORT_FILE_PATH); builder.ConfigCommonReinforcedTypings(EXPORT_FILE_PATH, FILE_NAME, errorsTypes); + builder.Substitute(typeof(IFormFile), new RtSimpleTypeName("any")); // DTO and Entites builder.ExportAsInterfaces([ diff --git a/app/server/UserService/src/UserService.Application/Configs/MappingConfig.cs b/app/server/UserService/src/UserService.Application/Configs/MappingConfig.cs deleted file mode 100644 index 071400414..000000000 --- a/app/server/UserService/src/UserService.Application/Configs/MappingConfig.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AutoMapper; -using Contract.DTOs.UserDTO; -using Google.Protobuf.Collections; -using UserService.Application.Configs.MapperConverters; -using UserService.Domain.Entities; -using UserService.Domain.Responses; - -namespace UserService.Application.Configs; - -public class MappingConfig -{ - public static MapperConfiguration RegisterMaps() - { - var mappingConfig = new MapperConfiguration(config => - { - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - config.CreateMap().ReverseMap(); - - - // Grpc mapping - config.CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); - config.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); - }); - - - return mappingConfig; - } -} diff --git a/app/server/UserService/src/UserService.Application/Configs/UserProfile.cs b/app/server/UserService/src/UserService.Application/Configs/UserProfile.cs new file mode 100644 index 000000000..9a4bc6cfc --- /dev/null +++ b/app/server/UserService/src/UserService.Application/Configs/UserProfile.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Contract.DTOs.UserDTO; +using Google.Protobuf.Collections; +using UserService.Application.Configs.MapperConverters; +using UserService.Domain.Entities; +using UserService.Domain.Responses; + +namespace UserService.Application.Configs; + +public class UserProfile : Profile +{ + public UserProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + + // Grpc mapping + CreateMap(typeof(List<>), typeof(RepeatedField<>)).ConvertUsing(typeof(ListToRepeatedFieldConverter<,>)); + CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListConverter<,>)); + } +} + diff --git a/app/server/UserService/src/UserService.Application/DependencyInjection.cs b/app/server/UserService/src/UserService.Application/DependencyInjection.cs index f5a6675ee..a13412be9 100644 --- a/app/server/UserService/src/UserService.Application/DependencyInjection.cs +++ b/app/server/UserService/src/UserService.Application/DependencyInjection.cs @@ -14,9 +14,12 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection { services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); // Register automapper - IMapper mapper = MappingConfig.RegisterMaps().CreateMapper(); - services.AddSingleton(mapper); - services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddAutoMapper( + cfg => + { + cfg.LicenseKey = DotNetEnv.Env.GetString("LUCKYPENNYSOFTWARE_LICENSE_KEY", "Not Found"); + }, + AppDomain.CurrentDomain.GetAssemblies()); services.AddGrpcClientService(); return services; } diff --git a/app/server/UserService/src/UserService.Application/UserService.Application.csproj b/app/server/UserService/src/UserService.Application/UserService.Application.csproj index 2ec124a51..eb9ea7b30 100644 --- a/app/server/UserService/src/UserService.Application/UserService.Application.csproj +++ b/app/server/UserService/src/UserService.Application/UserService.Application.csproj @@ -13,7 +13,7 @@ - + diff --git a/scripts/local/apply-all-migrations.sh b/scripts/local/apply-all-migrations.sh index 42894e37b..d90484f40 100755 --- a/scripts/local/apply-all-migrations.sh +++ b/scripts/local/apply-all-migrations.sh @@ -1,6 +1,31 @@ #!/bin/bash -. ./scripts/lib.sh && check_docker +. ./scripts/lib.sh + +TARGET_ENV=$1 +ENV_FILE=".env" +case "$TARGET_ENV" in + "dev"|"") + ENV_FILE=".env" + check_docker + ;; + "staging" | "production") + ENV_FILE=".env.$TARGET_ENV" + ;; + *) + # Invalid argument + printf "${DANGER}Invalid argument: '$TARGET_ENV'. Usage: $0 [-psh] [dev|staging|production]${NC}\n" + exit 1 + ;; +esac +echo "Migrating database for environment: $ENV_FILE" + +if [ ! -f "$ENV_FILE" ]; then + err_env_missing + exit +fi + +export $(grep -v '^#' $ENV_FILE | xargs) project_root=$(pwd) @@ -14,12 +39,13 @@ update_database() { local project=$2 local name=$3 - if [ -f $env_path ]; then - # Export each line as an environment variable - export $(grep -v '^#' .env | xargs) - else - echo ".env file not found." + if [[ ! -f "$env_path" ]]; then + echo "$env_path file not found." + return 1 fi + + # Export each line as an environment variable + export $(grep -v '^#' $env_path | xargs) if [[ " ${POSTGRES_REQUIRED_SERVICES[@]} " =~ " ${name} " ]]; then echo -e "${INFO}Running Postgresql migrations for ${name}...${NC}" @@ -34,8 +60,8 @@ update_database() { fi } -update_database "./app/server/IdentityService/.env" "./app/server/IdentityService/src/DuendeIdentityServer" "Identity" -update_database "./app/server/UserService/.env" "./app/server/UserService/src/UserService.API" "User" -update_database "./app/server/RecipeService/.env" "./app/server/RecipeService/src/RecipeService.API" "Recipe" -update_database "./app/server/NotificationService/.env" "./app/server/NotificationService/src/NotificationService.API" "Notification" -update_database "./app/server/TrackingService/.env" "./app/server/TrackingService/src/TrackingService.API" "Tracking" \ No newline at end of file +update_database "./app/server/IdentityService/$ENV_FILE" "./app/server/IdentityService/src/DuendeIdentityServer" "Identity" +update_database "./app/server/UserService/$ENV_FILE" "./app/server/UserService/src/UserService.API" "User" +update_database "./app/server/RecipeService/$ENV_FILE" "./app/server/RecipeService/src/RecipeService.API" "Recipe" +update_database "./app/server/NotificationService/$ENV_FILE" "./app/server/NotificationService/src/NotificationService.API" "Notification" +update_database "./app/server/TrackingService/$ENV_FILE" "./app/server/TrackingService/src/TrackingService.API" "Tracking" \ No newline at end of file