diff --git a/.gitignore b/.gitignore index 1eedd164e..cadc2946b 100644 --- a/.gitignore +++ b/.gitignore @@ -271,3 +271,6 @@ paket-files/ # Environment Specific app settings appsettings.*.json secrets.config + +# Logs +Gordon360/Logs/* \ No newline at end of file diff --git a/Gordon360/Documentation/Gordon360.xml b/Gordon360/Documentation/Gordon360.xml index 7f3a77b1f..b8b60c986 100644 --- a/Gordon360/Documentation/Gordon360.xml +++ b/Gordon360/Documentation/Gordon360.xml @@ -148,28 +148,6 @@ - - - Get all admins - - - A list of all admins - - - Server makes call to the database and returns all admins - - - - Create a new admin to be added to database - The admin item containing all required and relevant information - - Posts a new admin to the server to be added into the database - - - Delete an existing admin - The username of the user to demote - Calls the server to make a call and remove the given admin from the database - Return a list majors. @@ -1476,7 +1454,7 @@ Activity Info and ACtivity may be talked about interchangeably. - + Service Class that facilitates data transactions between the ActivitiesController and the ACT_INFO database model. ACT_INFO is basically a copy of the ACT_CLUB_DEF domain model in TmsEPrd but with extra fields that we want to store (activity image, blurb etc...) @@ -1573,50 +1551,6 @@ The activity code activity private or not - - - Service class to facilitate interacting with the Admin table. - - - - - Service class to facilitate interacting with the Admin table. - - - - - Fetches all the administrators from the database - - Returns a list of administrators. If no administrators were found, an empty list is returned. - - - - Fetches a specific admin from the database - - Returns a list of administrators. If no administrators were found, an empty list is returned. - - - - Adds a new Administrator record to storage. Since we can't establish foreign key constraints and relationships on the database side, - we do it here by using the validateAdmin() method. - - The admin to be added - The newly added Admin object - - - - Delete the admin whose id is specified by the parameter. - - The username of the admin to demote - The admin that was just deleted - - - - Helper method to Validate an admin - - The admin to validate - True if the admin is valid. Throws ResourceNotFoundException if not. Exception is cauth in an Exception Filter - Service class that facilitates data (specifically, site content) passing between the ContentManagementController and the database model. diff --git a/Gordon360/Gordon360.csproj b/Gordon360/Gordon360.csproj index 24fe5cae9..58f85ebe4 100644 --- a/Gordon360/Gordon360.csproj +++ b/Gordon360/Gordon360.csproj @@ -63,6 +63,11 @@ + + + + + @@ -71,6 +76,9 @@ + + + OnBuildSuccess diff --git a/Gordon360/Program.cs b/Gordon360/Program.cs index 85672b73d..5b5510ad8 100644 --- a/Gordon360/Program.cs +++ b/Gordon360/Program.cs @@ -12,109 +12,137 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Identity.Web; using Microsoft.OpenApi.Models; +using Serilog; +using Serilog.Formatting.Compact; using System; using System.Collections.Generic; using System.IO; -using RecIM = Gordon360.Services.RecIM; -var builder = WebApplication.CreateBuilder(args); +const string CorsPolicy = "360UI"; -var azureConfig = builder.Configuration.GetSection("AzureAd").Get(); +Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateBootstrapLogger(); -// Add services to the container. -builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, "AzureAd"); - -builder.Services.AddControllers(options => +try { - options.OutputFormatters.RemoveType(); // Return strings as application/json instead of text/plain - options.OutputFormatters.RemoveType(); // Return null as 200 Ok null instead of 204 No Content -}).AddNewtonsoftJson(options => options.UseMemberCasing()); + var builder = WebApplication.CreateBuilder(args); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(c => -{ - c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + builder.Services.AddSerilog((services, lc) => lc + .ReadFrom.Configuration(builder.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File(new CompactJsonFormatter(), "./Logs/info.json", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 14, rollOnFileSizeLimit: true) + .WriteTo.File(new CompactJsonFormatter(), "./Logs/error.json", restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error, rollingInterval: RollingInterval.Day)); + + builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, "AzureAd"); + + builder.Services.AddControllers(options => + { + options.OutputFormatters.RemoveType(); // Return strings as application/json instead of text/plain + options.OutputFormatters.RemoveType(); // Return null as 200 Ok null instead of 204 No Content + }).AddNewtonsoftJson(options => options.UseMemberCasing()); + + builder.Services.AddEndpointsApiExplorer(); + + var azureConfig = builder.Configuration.GetSection("AzureAd").Get(); + + builder.Services.AddSwaggerGen(c => { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() + c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { - AuthorizationCode = new OpenApiOAuthFlow() + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows() { - AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{azureConfig.TenantId}/oauth2/v2.0/authorize"), - TokenUrl = new Uri($"https://login.microsoftonline.com/{azureConfig.TenantId}/oauth2/v2.0/token"), - Scopes = new Dictionary { + AuthorizationCode = new OpenApiOAuthFlow() + { + AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{azureConfig.TenantId}/oauth2/v2.0/authorize"), + TokenUrl = new Uri($"https://login.microsoftonline.com/{azureConfig.TenantId}/oauth2/v2.0/token"), + Scopes = new Dictionary { { $"{azureConfig.Audience}/access_as_user", "Access 360 as you." } } + } } - } + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement() { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Type = ReferenceType.SecurityScheme, + Id = "oauth2" + }, + Scheme = "oauth2", + Name = "oauth2", + In = ParameterLocation.Header + }, + new List() + } + }); }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement() { + + builder.Services.AddCors(p => p.AddPolicy(name: CorsPolicy, corsBuilder => { - new OpenApiSecurityScheme { - Reference = new OpenApiReference { - Type = ReferenceType.SecurityScheme, - Id = "oauth2" - }, - Scheme = "oauth2", - Name = "oauth2", - In = ParameterLocation.Header - }, - new List < string > () - } -}); -} -); + corsBuilder.WithOrigins(builder.Configuration.GetValue("AllowedOrigin")).AllowAnyMethod().AllowAnyHeader(); + })); -string corsPolicy = "360UI"; -builder.Services.AddCors(p => p.AddPolicy(name: corsPolicy, corsBuilder => -{ - corsBuilder.WithOrigins(builder.Configuration.GetValue("AllowedOrigin")).AllowAnyMethod().AllowAnyHeader(); -})); + builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("CCT")) + ).AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("MyGordon")) + ).AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("webSQL")) + ); -builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("CCT")) -).AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("MyGordon")) -).AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("webSQL")) -); + builder.Services.Add360Services(); + builder.Services.AddHostedService(); + builder.Services.AddScoped(); -builder.Services.Add360Services(); -builder.Services.AddHostedService(); -builder.Services.AddScoped(); + builder.Services.AddMemoryCache(); -builder.Services.AddMemoryCache(); + var app = builder.Build(); -var app = builder.Build(); + // Configure the HTTP request pipeline. -// Configure the HTTP request pipeline. + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.OAuthClientId(azureConfig.ClientId); + c.OAuthScopes($"{azureConfig.Audience}/access_as_user"); + c.OAuthUsePkce(); + }); -app.UseSwagger(); -app.UseSwaggerUI(c => -{ - c.OAuthClientId(azureConfig.ClientId); - c.OAuthScopes($"{azureConfig.Audience}/access_as_user"); - c.OAuthUsePkce(); -}); + app.UseSerilogRequestLogging(); -app.UseStaticFiles(new StaticFileOptions -{ - FileProvider = new PhysicalFileProvider( - Path.Combine(builder.Environment.ContentRootPath, "browseable")), - RequestPath = "/browseable" -}); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider( + Path.Combine(builder.Environment.ContentRootPath, "browseable")), + RequestPath = "/browseable" + }); -app.UseRouting(); + app.UseRouting(); -app.UseAuthentication(); -app.UseAuthorization(); + app.UseAuthentication(); + app.UseAuthorization(); -app.UseCors(corsPolicy); + app.UseCors(CorsPolicy); -app.MapControllers(); + app.MapControllers(); + + app.Run(); + + // Only runs once the app shutsdown + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "An unhandled exception occurred during startup"); + return 1; +} +finally +{ + Log.CloseAndFlush(); +} -app.Run(); diff --git a/Gordon360/appsettings.json b/Gordon360/appsettings.json index 9f0dbb20f..3fa7d86fb 100644 --- a/Gordon360/appsettings.json +++ b/Gordon360/appsettings.json @@ -1,6 +1,6 @@ { "AllowedHosts": "localhost;360.gordon.edu", - "AllowedOrigin": "", + "AllowedOrigin": "", "AzureAd": { "Instance": "", "ClientId": "", @@ -37,5 +37,16 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft.AspNetCore.Mvc": "Warning", + "Microsoft.AspNetCore.Routing": "Warning", + "Microsoft.AspNetCore.Hosting": "Warning" + } + } + }, "SmtpHost": "smtp.gordon.edu" } \ No newline at end of file