diff --git a/.pipeline/lib/config.js b/.pipeline/lib/config.js index 5a115930..86100703 100644 --- a/.pipeline/lib/config.js +++ b/.pipeline/lib/config.js @@ -1,7 +1,7 @@ "use strict"; const options = require("@bcgov/pipeline-cli").Util.parseArguments(); const changeId = options.pr; // aka pull-request -const version = "2.3.12"; +const version = "2.3.14"; const name = "transaction"; Object.assign(options.git, { owner: "ychung-mot", repository: "TransAction" }); diff --git a/README.md b/README.md index 8a0cf6f5..21798a5d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ TransAction is a voluntary initiative sponsored by the Ministry of Transportatio ## Requirements - .NET 7 -- Node.js 8+ +- NodeJS 12+ - Microsoft SQL Server 2012+ - Pre-configured Keycloak Server diff --git a/api/TransAction.API/Controllers/ImageController.cs b/api/TransAction.API/Controllers/ImageController.cs index 032a9eb4..c553c901 100644 --- a/api/TransAction.API/Controllers/ImageController.cs +++ b/api/TransAction.API/Controllers/ImageController.cs @@ -30,20 +30,10 @@ public ImageController(IHttpContextAccessor httpContextAccessor, ILogger image = Image.Load(bytes)) + using (Image image = Image.Load(bytes)) { int maxWidthOrLength = Math.Max(image.Width, image.Height); diff --git a/api/TransAction.API/Program.cs b/api/TransAction.API/Program.cs index ab57e9b9..775738b8 100644 --- a/api/TransAction.API/Program.cs +++ b/api/TransAction.API/Program.cs @@ -1,47 +1,171 @@ -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.IdentityModel.Logging; using Serilog; using System; using System.IO; +using TransAction.API.Authentication; +using TransAction.Data.Models; +using TransAction.Data.Repositories.Interfaces; +using TransAction.Data.Repositories; +using TransAction.Data.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Authorization; +using TransAction.API.Helpers; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using TransAction.API.Extensions; +using System.Linq; +using Microsoft.AspNetCore.Http; +using System.Net.Mime; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; -namespace TransAction.API +var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) + .AddEnvironmentVariables() + .Build(); + +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .CreateLogger(); + +// Only used for app.ConfigureExceptionHandler() +var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); +var logger = loggerFactory.CreateLogger("Logger"); + +var builder = WebApplication.CreateBuilder(args); + +// Register services here +builder.Host.UseSerilog(Log.Logger); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddAutoMapper(typeof(Program).Assembly); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var ConnectionString = configuration["CONNECTION_STRING"]; +builder.Services.AddDbContext(opt => + opt.UseSqlServer(ConnectionString)); +IdentityModelEventSource.ShowPII = true; + +builder.Services.AddAuthentication(options => { - public class Program - { - public static IConfiguration Configuration { get; } = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) - .AddEnvironmentVariables() - .Build(); - - public static int Main(string[] args) + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) + .AddKeycloakAuth(new KeycloakAuthenticationOptions() { - Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(Configuration) - .CreateLogger(); + Authority = configuration["JWT_AUTHORITY"], + Audience = configuration["JWT_AUDIENCE"] + } + ); - try - { - Log.Information("Starting web host"); - CreateWebHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly"); - return 1; - } - finally - { - Log.CloseAndFlush(); +builder.Services.AddControllers(options => +{ + options.Filters.Add( + new AuthorizeFilter( + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build() + ) + ); +}).AddNewtonsoftJson(options => +{ + options.SerializerSettings.Converters.Add(new TrimmingConverter()); +}); + +builder.Services.AddHealthChecks().AddSqlServer( + ConnectionString, + name: "DB-Check", + failureStatus: HealthStatus.Degraded, + tags: new string[] { "sql", "db" } +); + +var app = builder.Build(); + +// Use services here +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseSerilogRequestLogging(); +app.ConfigureExceptionHandler(logger); + +var healthCheckOptions = new HealthCheckOptions +{ + ResponseWriter = async (c, r) => + { + c.Response.ContentType = MediaTypeNames.Application.Json; + var result = System.Text.Json.JsonSerializer.Serialize( + new { + checks = r.Entries.Select(e => + new { + description = e.Key, + status = e.Value.Status.ToString(), + tags = e.Value.Tags, + responseTime = e.Value.Duration.TotalMilliseconds + } + ), + totalResponseTime = r.TotalDuration.TotalMilliseconds } - } + ); + await c.Response.WriteAsync(result); + } +}; + +app.UseHealthChecks("/healthz", healthCheckOptions); +app.UseRouting(); +app.UseAuthentication(); +app.MapControllers(); + +app.Run(); + +/* +public class Program +{ + public static IConfiguration Configuration { get; } = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true) + .AddEnvironmentVariables() + .Build(); - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseSerilog(); + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(Configuration) + .CreateLogger(); + + try + { + Log.Information("Starting web host"); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .UseSerilog(); } +*/ \ No newline at end of file diff --git a/api/TransAction.API/Startup.cs b/api/TransAction.API/Startup.cs deleted file mode 100644 index a4a72c42..00000000 --- a/api/TransAction.API/Startup.cs +++ /dev/null @@ -1,128 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Authorization; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Serilog; -using System.Linq; -using System.Net.Mime; -using TransAction.API.Authentication; -using TransAction.API.Extensions; -using TransAction.API.Helpers; -using TransAction.Data.Models; -using TransAction.Data.Repositories; -using TransAction.Data.Repositories.Interfaces; -using TransAction.Data.Services; -using Microsoft.IdentityModel.Logging; - -namespace TransAction.API -{ - public class Startup - { - private readonly ILogger _logger; - - public Startup(IConfiguration configuration, ILogger logger) - { - Configuration = configuration; - _logger = logger; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - - //Adding services - services.AddHttpContextAccessor(); - - services.AddAutoMapper(typeof(Startup).Assembly); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - var ConnectionString = Configuration["CONNECTION_STRING"]; - services.AddDbContext(opt => - opt.UseSqlServer(ConnectionString)); - IdentityModelEventSource.ShowPII = true; - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddKeycloakAuth(new KeycloakAuthenticationOptions() { Authority = Configuration["JWT_AUTHORITY"], Audience = Configuration["JWT_AUDIENCE"] }); - - services.AddControllers(options => - { - options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) - .AddNewtonsoftJson(options => - { - options.SerializerSettings.Converters.Add(new TrimmingConverter()); - }); - - services.AddHealthChecks() - .AddSqlServer(ConnectionString, name: "DB-Check", failureStatus: HealthStatus.Degraded, tags: new string[] { "sql", "db" }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - - } - - app.UseSerilogRequestLogging(); - app.ConfigureExceptionHandler(_logger); - - var healthCheckOptions = new HealthCheckOptions - { - ResponseWriter = async (c, r) => - { - c.Response.ContentType = MediaTypeNames.Application.Json; - var result = System.Text.Json.JsonSerializer.Serialize( - new - { - checks = r.Entries.Select(e => - new - { - description = e.Key, - status = e.Value.Status.ToString(), - tags = e.Value.Tags, - responseTime = e.Value.Duration.TotalMilliseconds - }), - totalResponseTime = r.TotalDuration.TotalMilliseconds - }); - await c.Response.WriteAsync(result); - } - }; - - app.UseHealthChecks("/healthz", healthCheckOptions); - - app.UseRouting(); - app.UseAuthentication(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} diff --git a/api/TransAction.API/TransAction.API.csproj b/api/TransAction.API/TransAction.API.csproj index f44dab4c..911bb649 100644 --- a/api/TransAction.API/TransAction.API.csproj +++ b/api/TransAction.API/TransAction.API.csproj @@ -1,37 +1,37 @@ - netcoreapp3.1 + net7.0 InProcess https://github.com/bcgov/TransAction.git git - 2.3.13 + 2.3.14 The API server for the TransAction System Copyright© 2012, Province of British Columbia. - 2.3.13.0 - 2.3.13.0 + 2.3.14.0 + 2.3.14.0 - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers - - - - - - - - - - + + + + + + + + + + diff --git a/api/TransAction.API/appsettings.json b/api/TransAction.API/appsettings.json index 1a15627d..29307cbd 100644 --- a/api/TransAction.API/appsettings.json +++ b/api/TransAction.API/appsettings.json @@ -1,6 +1,6 @@ { "Constants": { - "Version": "2.3.13" + "Version": "2.3.14" }, "Serilog": { "MinimumLevel": { @@ -19,7 +19,7 @@ ] }, "AllowedHosts": "*", - "CONNECTION_STRING": "Server=localhost;User Id=;Password=;Database=TransAction;", + "CONNECTION_STRING": "Server=localhost;Database=TransAction;user=;password=;MultipleActiveResultSets=true;TrustServerCertificate=True", "JWT_AUTHORITY": "https://dev.loginproxy.gov.bc.ca/auth/realms/", "JWT_AUDIENCE": "", "Kestrel": { diff --git a/api/TransAction.Data/Models/TransActionContext.cs b/api/TransAction.Data/Models/TransActionContext.cs index 6465ff82..ce68c445 100644 --- a/api/TransAction.Data/Models/TransActionContext.cs +++ b/api/TransAction.Data/Models/TransActionContext.cs @@ -1,4 +1,7 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; using System.Linq; @@ -149,10 +152,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_EVENT_TEAM"); entity.HasIndex(e => e.EventId) - .HasName("IX_FK_EVENT_TEAM_EVENT"); + .HasDatabaseName("IX_FK_EVENT_TEAM_EVENT"); entity.HasIndex(e => e.TeamId) - .HasName("IX_FK_EVENT_TEAM_TEAM"); + .HasDatabaseName("IX_FK_EVENT_TEAM_TEAM"); entity.Property(e => e.EventTeamId).HasColumnName("EVENT_TEAM_ID"); @@ -205,10 +208,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_EVENT_USER"); entity.HasIndex(e => e.EventId) - .HasName("IX_FK_EVENT_USER_EVENT"); + .HasDatabaseName("IX_FK_EVENT_USER_EVENT"); entity.HasIndex(e => e.UserId) - .HasName("IX_FK_EVENT_USER_USER"); + .HasDatabaseName("IX_FK_EVENT_USER_USER"); entity.Property(e => e.EventUserId).HasColumnName("EVENT_USER_ID"); @@ -339,10 +342,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_MEMBER_REQ"); entity.HasIndex(e => e.TeamId) - .HasName("IX_FK_MEMBER_REQ_TEAM"); + .HasDatabaseName("IX_FK_MEMBER_REQ_TEAM"); entity.HasIndex(e => e.UserId) - .HasName("IX_FK_MEMBER_REQ_USER"); + .HasDatabaseName("IX_FK_MEMBER_REQ_USER"); entity.Property(e => e.MemberReqId).HasColumnName("MEMBER_REQ_ID"); @@ -606,10 +609,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_TOPIC_MESSAGE"); entity.HasIndex(e => e.TopicId) - .HasName("IX_FK_TOPIC_MESSAGE_TOPIC"); + .HasDatabaseName("IX_FK_TOPIC_MESSAGE_TOPIC"); entity.HasIndex(e => e.UserId) - .HasName("IX_FK_TOPIC_USER"); + .HasDatabaseName("IX_FK_TOPIC_USER"); entity.Property(e => e.TopicMessageId).HasColumnName("TOPIC_MESSAGE_ID"); @@ -667,14 +670,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_USER"); entity.HasIndex(e => e.Guid) - .HasName("UQ__TRA_USER__15B69B8FCA3954AF") + .HasDatabaseName("UQ__TRA_USER__15B69B8FCA3954AF") .IsUnique(); entity.HasIndex(e => e.RoleId) - .HasName("IX_FK_USER_ROLE"); + .HasDatabaseName("IX_FK_USER_ROLE"); entity.HasIndex(e => e.TeamId) - .HasName("IX_FK_USER_TEAM"); + .HasDatabaseName("IX_FK_USER_TEAM"); entity.Property(e => e.UserId).HasColumnName("USER_ID"); @@ -781,10 +784,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("TRA_USER_ACTIVITY"); entity.HasIndex(e => e.ActivityId) - .HasName("IX_FK_USER_ACTIVITY_ACTIVITY"); + .HasDatabaseName("IX_FK_USER_ACTIVITY_ACTIVITY"); entity.HasIndex(e => e.UserId) - .HasName("IX_FK_USER_ACTIVITY_USER"); + .HasDatabaseName("IX_FK_USER_ACTIVITY_USER"); entity.Property(e => e.UserActivityId).HasColumnName("USER_ACTIVITY_ID"); @@ -863,6 +866,35 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }); } + public class BlankTriggerAddingConvention : IModelFinalizingConvention + { + public virtual void ProcessModelFinalizing( + IConventionModelBuilder modelBuilder, + IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); + if (table != null + && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)) + { + entityType.Builder.HasTrigger(table.Value.Name + "_Trigger"); + } + + foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table)) + { + if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null)) + { + entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger"); + } + } + } + } + } + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention()); + } } } diff --git a/api/TransAction.Data/TransAction.Data.csproj b/api/TransAction.Data/TransAction.Data.csproj index 6ea78bb9..69e65989 100644 --- a/api/TransAction.Data/TransAction.Data.csproj +++ b/api/TransAction.Data/TransAction.Data.csproj @@ -1,14 +1,14 @@ - + - netcoreapp3.1 + net7.0 - - - - + + + + diff --git a/client/package-lock.json b/client/package-lock.json index 85e2b305..b7e51365 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "transaction-client", - "version": "2.3.6", + "version": "2.3.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "transaction-client", - "version": "2.3.6", + "version": "2.3.14", "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.30", "@fortawesome/free-solid-svg-icons": "^5.14.0", diff --git a/client/package.json b/client/package.json index b1782057..4f8169bf 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "transaction-client", - "version": "2.3.6", + "version": "2.3.14", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.30", diff --git a/openshift/api-build-config.yaml b/openshift/api-build-config.yaml index 283b87d5..87066c92 100644 --- a/openshift/api-build-config.yaml +++ b/openshift/api-build-config.yaml @@ -9,7 +9,8 @@ objects: - apiVersion: "v1" kind: ImageStream metadata: - name: dotnet-31-rhel7 + # name: dotnet-31-rhel7 + name: dotnet-70 spec: lookupPolicy: local: false @@ -17,8 +18,10 @@ objects: - annotations: null from: kind: DockerImage - name: registry.redhat.io/dotnet/dotnet-31-rhel7:3.1-24 - name: "3.1" + # name: registry.redhat.io/dotnet/dotnet-31-rhel7:3.1-24 + name: registry.redhat.io/rhel8/dotnet-70:7.0-18 + # name: "3.1" + name: "7.0" referencePolicy: type: Local - apiVersion: v1 @@ -65,7 +68,8 @@ objects: value: TransAction.API/TransAction.API.csproj from: kind: ImageStreamTag - name: "dotnet-31-rhel7:3.1" + # name: "dotnet-31-rhel7:3.1" + name: "dotnet-70:7.0" type: Source parameters: - description: Name of the project (TransAction) diff --git a/openshift/configmaps/api-appsettings.yaml b/openshift/configmaps/api-appsettings.yaml index de264959..9d01adc6 100644 --- a/openshift/configmaps/api-appsettings.yaml +++ b/openshift/configmaps/api-appsettings.yaml @@ -8,7 +8,7 @@ objects: appsettings.json: |- { "Constants": { - "Version": "2.3.13" + "Version": "2.3.14" }, "Serilog": { "MinimumLevel": { @@ -27,7 +27,7 @@ objects: ] }, "AllowedHosts": "*", - "CONNECTION_STRING": "Server=localhost;User Id=;Password=;Database=TransAction;", + "CONNECTION_STRING": "Server=localhost;Database=TransAction;user=;password=;MultipleActiveResultSets=true;TrustServerCertificate=True", "JWT_AUTHORITY": "https://dev.loginproxy.gov.bc.ca/auth/realms/", "JWT_AUDIENCE": "", "Kestrel": {