diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index cacfe55..f0ef6c7 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -20,14 +20,44 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Get tag run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - name: Build api image + - name: Build images run: | - docker build -t zme-api -f Memphis.API/Dockerfile . + docker build -t api -f Memphis.API/Dockerfile . + docker build -t airports-job -f Memphis.Jobs.Airports/Dockerfile . + docker build -t atc-job -f Memphis.Jobs.ATC/Dockerfile . + docker build -t datafeed-job -f Memphis.Jobs.Datafeed/Dockerfile . + docker build -t events-job -f Memphis.Jobs.Events/Dockerfile . + docker build -t roster-job -f Memphis.Jobs.Roster/Dockerfile . + docker build -t solocerts-job -f Memphis.Jobs.SoloCerts/Dockerfile . - name: Tag images run: | - docker tag zme-api ghcr.io/memphis-artcc/api:latest - docker tag zme-api ghcr.io/memphis-artcc/api:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/api:latest + docker tag api ghcr.io/memphis-artcc/api:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/airports-job:latest + docker tag api ghcr.io/memphis-artcc/airports-job:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/atc-job:latest + docker tag api ghcr.io/memphis-artcc/atc-job:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/datafeed-job:latest + docker tag api ghcr.io/memphis-artcc/datafeed-job:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/events-job:latest + docker tag api ghcr.io/memphis-artcc/events-job:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/roster-job:latest + docker tag api ghcr.io/memphis-artcc/roster-job:${{ env.RELEASE_VERSION }} + docker tag api ghcr.io/memphis-artcc/solocerts-job:latest + docker tag api ghcr.io/memphis-artcc/solocerts-job:${{ env.RELEASE_VERSION }} - name: Push images run: | - docker push ghcr.io/memphis-artcc/api:latest - docker push ghcr.io/memphis-artcc/api:${{ env.RELEASE_VERSION }} \ No newline at end of file + docker push api ghcr.io/memphis-artcc/api:latest + docker push api ghcr.io/memphis-artcc/api:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/airports-job:latest + docker push api ghcr.io/memphis-artcc/airports-job:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/atc-job:latest + docker push api ghcr.io/memphis-artcc/atc-job:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/datafeed-job:latest + docker push api ghcr.io/memphis-artcc/datafeed-job:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/events-job:latest + docker push api ghcr.io/memphis-artcc/events-job:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/roster-job:latest + docker push api ghcr.io/memphis-artcc/roster-job:${{ env.RELEASE_VERSION }} + docker push api ghcr.io/memphis-artcc/solocerts-job:latest + docker push api ghcr.io/memphis-artcc/solocerts-job:${{ env.RELEASE_VERSION }} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 403b04b..837e3e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,4 +12,10 @@ jobs: uses: actions/checkout@v2 - name: Build api image run: | - docker build -t api -f Memphis.API/Dockerfile . \ No newline at end of file + docker build -t api -f Memphis.API/Dockerfile . + docker build -t airports-job -f Memphis.Jobs.Airports/Dockerfile . + docker build -t atc-job -f Memphis.Jobs.ATC/Dockerfile . + docker build -t datafeed-job -f Memphis.Jobs.Datafeed/Dockerfile . + docker build -t events-job -f Memphis.Jobs.Events/Dockerfile . + docker build -t roster-job -f Memphis.Jobs.Roster/Dockerfile . + docker build -t solocerts-job -f Memphis.Jobs.SoloCerts/Dockerfile . \ No newline at end of file diff --git a/Memphis.API/Controllers/AirportsController.cs b/Memphis.API/Controllers/AirportsController.cs index fe9195a..8847a2b 100644 --- a/Memphis.API/Controllers/AirportsController.cs +++ b/Memphis.API/Controllers/AirportsController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Memphis.Shared.Utils; @@ -54,7 +54,7 @@ public async Task>> CreateAirport(AirportDto payl return StatusCode(401); } - ValidationResult validation = await _validator.ValidateAsync(payload); + var validation = await _validator.ValidateAsync(payload); if (!validation.IsValid) { return BadRequest(new Response> @@ -65,7 +65,7 @@ public async Task>> CreateAirport(AirportDto payl }); } - Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry result = await _context.Airports.AddAsync(new Airport + var result = await _context.Airports.AddAsync(new Airport { Name = payload.Name, Icao = payload.Icao, @@ -95,7 +95,7 @@ public async Task>>> GetAirports() { try { - List result = await _context.Airports.ToListAsync(); + var result = await _context.Airports.ToListAsync(); return Ok(new Response> { StatusCode = 200, diff --git a/Memphis.API/Controllers/AuthController.cs b/Memphis.API/Controllers/AuthController.cs index 2245a2f..26d03e6 100644 --- a/Memphis.API/Controllers/AuthController.cs +++ b/Memphis.API/Controllers/AuthController.cs @@ -1,6 +1,6 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos.auth; using Memphis.Shared.Dtos.Auth; using Memphis.Shared.Enums; diff --git a/Memphis.API/Controllers/CommentsController.cs b/Memphis.API/Controllers/CommentsController.cs index 1df1bd1..a0a98a5 100644 --- a/Memphis.API/Controllers/CommentsController.cs +++ b/Memphis.API/Controllers/CommentsController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Memphis.Shared.Utils; diff --git a/Memphis.API/Controllers/EmailLogsController.cs b/Memphis.API/Controllers/EmailLogsController.cs index feaef2a..6b9a3c5 100644 --- a/Memphis.API/Controllers/EmailLogsController.cs +++ b/Memphis.API/Controllers/EmailLogsController.cs @@ -1,7 +1,7 @@ using FluentValidation; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; diff --git a/Memphis.API/Controllers/EventPositionsController.cs b/Memphis.API/Controllers/EventPositionsController.cs index 168bec6..12fd6ed 100644 --- a/Memphis.API/Controllers/EventPositionsController.cs +++ b/Memphis.API/Controllers/EventPositionsController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Memphis.Shared.Utils; diff --git a/Memphis.API/Controllers/EventRegistrationController.cs b/Memphis.API/Controllers/EventRegistrationController.cs index a63986e..36f93d5 100644 --- a/Memphis.API/Controllers/EventRegistrationController.cs +++ b/Memphis.API/Controllers/EventRegistrationController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Enums; using Memphis.Shared.Models; diff --git a/Memphis.API/Controllers/EventsController.cs b/Memphis.API/Controllers/EventsController.cs index 473599f..6a32e9f 100644 --- a/Memphis.API/Controllers/EventsController.cs +++ b/Memphis.API/Controllers/EventsController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Memphis.Shared.Utils; diff --git a/Memphis.API/Controllers/FeedbackController.cs b/Memphis.API/Controllers/FeedbackController.cs index e5319e3..f80191c 100644 --- a/Memphis.API/Controllers/FeedbackController.cs +++ b/Memphis.API/Controllers/FeedbackController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Enums; using Memphis.Shared.Models; diff --git a/Memphis.API/Controllers/FilesController.cs b/Memphis.API/Controllers/FilesController.cs index e745b71..a6ba659 100644 --- a/Memphis.API/Controllers/FilesController.cs +++ b/Memphis.API/Controllers/FilesController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Enums; using Memphis.Shared.Utils; diff --git a/Memphis.API/Controllers/HoursController.cs b/Memphis.API/Controllers/HoursController.cs index 858d441..e63ad35 100644 --- a/Memphis.API/Controllers/HoursController.cs +++ b/Memphis.API/Controllers/HoursController.cs @@ -1,5 +1,5 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Mvc; diff --git a/Memphis.API/Controllers/NotificationsController.cs b/Memphis.API/Controllers/NotificationsController.cs index 79dde8d..7c88aeb 100644 --- a/Memphis.API/Controllers/NotificationsController.cs +++ b/Memphis.API/Controllers/NotificationsController.cs @@ -1,6 +1,6 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; diff --git a/Memphis.API/Controllers/OnlineControllersController.cs b/Memphis.API/Controllers/OnlineControllersController.cs index f599643..e8fce7a 100644 --- a/Memphis.API/Controllers/OnlineControllersController.cs +++ b/Memphis.API/Controllers/OnlineControllersController.cs @@ -1,5 +1,5 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; +using Memphis.Shared.Data; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Mvc; diff --git a/Memphis.API/Controllers/OtsController.cs b/Memphis.API/Controllers/OtsController.cs index 9d535cc..09730d9 100644 --- a/Memphis.API/Controllers/OtsController.cs +++ b/Memphis.API/Controllers/OtsController.cs @@ -1,7 +1,7 @@ using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Enums; using Memphis.Shared.Utils; diff --git a/Memphis.API/Controllers/SessionsController.cs b/Memphis.API/Controllers/SessionsController.cs index 1b2c84e..ad6e485 100644 --- a/Memphis.API/Controllers/SessionsController.cs +++ b/Memphis.API/Controllers/SessionsController.cs @@ -1,6 +1,6 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/Memphis.API/Controllers/SettingsController.cs b/Memphis.API/Controllers/SettingsController.cs index 0cb74de..8969f3d 100644 --- a/Memphis.API/Controllers/SettingsController.cs +++ b/Memphis.API/Controllers/SettingsController.cs @@ -1,6 +1,8 @@ -using Memphis.API.Data; +using FluentValidation; +using FluentValidation.Results; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; @@ -19,17 +21,74 @@ public class SettingsController : ControllerBase private readonly DatabaseContext _context; private readonly RedisService _redisService; private readonly LoggingService _loggingService; + private readonly IValidator _validator; private readonly ISentryClient _sentryHub; private readonly ILogger _logger; public SettingsController(DatabaseContext context, RedisService redisService, LoggingService loggingService, - ISentryClient sentryHub, ILogger logger) + IValidator validator, ISentryClient sentryHub, ILogger logger) { _context = context; _redisService = redisService; _loggingService = loggingService; _sentryHub = sentryHub; _logger = logger; + _validator = validator; + } + + [HttpPost] + [Authorize(Roles = Constants.SeniorStaff)] + [ProducesResponseType(typeof(Response), 201)] + [ProducesResponseType(typeof(Response>), 400)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> AddFacility(Facility payload) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList)) + { + return StatusCode(401); + } + + var validation = await _validator.ValidateAsync(payload); + if (!validation.IsValid) + { + return BadRequest(new Response> + { + StatusCode = 400, + Message = "Validation failure", + Data = validation.Errors + }); + } + + if (!await _context.Facilities.AnyAsync(x => x.Identifier.Equals(payload.Identifier, StringComparison.OrdinalIgnoreCase))) + { + await _context.Facilities.AddAsync(payload); + await _context.SaveChangesAsync(); + + await _loggingService.AddWebsiteLog(Request, "Added facility", string.Empty, JsonConvert.SerializeObject(payload)); + + var facilities = await _context.Facilities.ToListAsync(); + return Ok(new Response> + { + StatusCode = 200, + Message = "Added facility", + Data = facilities + }); + } + return BadRequest(new Response + { + StatusCode = 400, + Message = "Facility already exists" + }); + } + catch (Exception ex) + { + _logger.LogError("AddFacility error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } } [HttpGet] diff --git a/Memphis.API/Controllers/StatsController.cs b/Memphis.API/Controllers/StatsController.cs index 5ffc796..1b65a02 100644 --- a/Memphis.API/Controllers/StatsController.cs +++ b/Memphis.API/Controllers/StatsController.cs @@ -1,5 +1,5 @@ -using Memphis.API.Data; -using Memphis.API.Extensions; +using Memphis.API.Extensions; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Enums; using Memphis.Shared.Models; diff --git a/Memphis.API/Controllers/TrainingMilestonesController.cs b/Memphis.API/Controllers/TrainingMilestonesController.cs index e136ed1..f49bd9e 100644 --- a/Memphis.API/Controllers/TrainingMilestonesController.cs +++ b/Memphis.API/Controllers/TrainingMilestonesController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; diff --git a/Memphis.API/Controllers/TrainingSchedulesController.cs b/Memphis.API/Controllers/TrainingSchedulesController.cs index d5ca15c..06edd8d 100644 --- a/Memphis.API/Controllers/TrainingSchedulesController.cs +++ b/Memphis.API/Controllers/TrainingSchedulesController.cs @@ -1,8 +1,8 @@ using FluentValidation; using FluentValidation.Results; -using Memphis.API.Data; using Memphis.API.Extensions; using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Memphis.Shared.Utils; diff --git a/Memphis.API/Extensions/HttpContextExtensions.cs b/Memphis.API/Extensions/HttpContextExtensions.cs index c737df7..5e75ab1 100644 --- a/Memphis.API/Extensions/HttpContextExtensions.cs +++ b/Memphis.API/Extensions/HttpContextExtensions.cs @@ -1,5 +1,5 @@ -using Memphis.API.Data; -using Memphis.API.Services; +using Memphis.API.Services; +using Memphis.Shared.Data; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.EntityFrameworkCore; diff --git a/Memphis.API/Memphis.API.csproj b/Memphis.API/Memphis.API.csproj index e5011b1..a2129d5 100644 --- a/Memphis.API/Memphis.API.csproj +++ b/Memphis.API/Memphis.API.csproj @@ -7,24 +7,24 @@ - + - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + @@ -32,8 +32,7 @@ - - + diff --git a/Memphis.API/Migrations/20240413042558_OnlineControllers.cs b/Memphis.API/Migrations/20240413042558_OnlineControllers.cs deleted file mode 100644 index cf72d76..0000000 --- a/Memphis.API/Migrations/20240413042558_OnlineControllers.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Memphis.API.Migrations -{ - /// - public partial class OnlineControllers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Duration", - table: "OnlineControllers", - type: "text", - nullable: false, - oldClrType: typeof(TimeSpan), - oldType: "interval"); - - migrationBuilder.UpdateData( - table: "Settings", - keyColumn: "Id", - keyValue: 1, - column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 4, 13, 4, 25, 56, 546, DateTimeKind.Unspecified).AddTicks(2855), new TimeSpan(0, 0, 0, 0, 0))); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Duration", - table: "OnlineControllers", - type: "interval", - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.UpdateData( - table: "Settings", - keyColumn: "Id", - keyValue: 1, - column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 1, 23, 3, 8, 4, 225, DateTimeKind.Unspecified).AddTicks(8735), new TimeSpan(0, 0, 0, 0, 0))); - } - } -} diff --git a/Memphis.API/Migrations/20240413144157_OnlineControllersStuff.cs b/Memphis.API/Migrations/20240413144157_OnlineControllersStuff.cs deleted file mode 100644 index 4585d8b..0000000 --- a/Memphis.API/Migrations/20240413144157_OnlineControllersStuff.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Memphis.API.Migrations -{ - /// - public partial class OnlineControllersStuff : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Cid", - table: "OnlineControllers", - type: "integer", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "Rating", - table: "OnlineControllers", - type: "integer", - nullable: false, - defaultValue: 0); - - migrationBuilder.UpdateData( - table: "Settings", - keyColumn: "Id", - keyValue: 1, - column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 4, 13, 14, 41, 56, 263, DateTimeKind.Unspecified).AddTicks(453), new TimeSpan(0, 0, 0, 0, 0))); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Cid", - table: "OnlineControllers"); - - migrationBuilder.DropColumn( - name: "Rating", - table: "OnlineControllers"); - - migrationBuilder.UpdateData( - table: "Settings", - keyColumn: "Id", - keyValue: 1, - column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 4, 13, 4, 25, 56, 546, DateTimeKind.Unspecified).AddTicks(2855), new TimeSpan(0, 0, 0, 0, 0))); - } - } -} diff --git a/Memphis.API/Program.cs b/Memphis.API/Program.cs index 64af788..d24b5f0 100644 --- a/Memphis.API/Program.cs +++ b/Memphis.API/Program.cs @@ -1,8 +1,8 @@ using dotenv.net; using FluentValidation; -using Memphis.API.Data; using Memphis.API.Services; using Memphis.API.Validators; +using Memphis.Shared.Data; using Memphis.Shared.Dtos; using Memphis.Shared.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -104,6 +104,7 @@ builder.Services.AddScoped, EventPositionValidator>(); builder.Services.AddScoped, EventRegistrationValidator>(); builder.Services.AddScoped, EventValidator>(); +builder.Services.AddScoped, FacilityValidator>(); builder.Services.AddScoped, FaqValidator>(); builder.Services.AddScoped, FeedbackValidator>(); builder.Services.AddScoped, FileValidator>(); diff --git a/Memphis.API/Services/LoggingService.cs b/Memphis.API/Services/LoggingService.cs index 74e1a3b..1d9cdfd 100644 --- a/Memphis.API/Services/LoggingService.cs +++ b/Memphis.API/Services/LoggingService.cs @@ -1,4 +1,4 @@ -using Memphis.API.Data; +using Memphis.Shared.Data; using Memphis.Shared.Models; namespace Memphis.API.Services; diff --git a/Memphis.API/Services/RedisService.cs b/Memphis.API/Services/RedisService.cs index fbca3df..87c53e4 100644 --- a/Memphis.API/Services/RedisService.cs +++ b/Memphis.API/Services/RedisService.cs @@ -1,4 +1,4 @@ -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using StackExchange.Redis; diff --git a/Memphis.API/Validators/FacilityValidator.cs b/Memphis.API/Validators/FacilityValidator.cs new file mode 100644 index 0000000..fa47e12 --- /dev/null +++ b/Memphis.API/Validators/FacilityValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; +using Memphis.Shared.Models; + +namespace Memphis.API.Validators; + +public class FacilityValidator : AbstractValidator +{ + public FacilityValidator() + { + RuleFor(x => x.Identifier).NotEmpty(); + } +} diff --git a/Memphis.Jobs.ATC/Dockerfile b/Memphis.Jobs.ATC/Dockerfile new file mode 100644 index 0000000..10c74b4 --- /dev/null +++ b/Memphis.Jobs.ATC/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.ATC/Memphis.Jobs.ATC.csproj", "Memphis.Jobs.ATC/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.ATC/Memphis.Jobs.ATC.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.ATC" +RUN dotnet build "./Memphis.Jobs.ATC.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.ATC.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.ATC.dll"] \ No newline at end of file diff --git a/Memphis.Jobs/Memphis.Jobs.csproj b/Memphis.Jobs.ATC/Memphis.Jobs.ATC.csproj similarity index 66% rename from Memphis.Jobs/Memphis.Jobs.csproj rename to Memphis.Jobs.ATC/Memphis.Jobs.ATC.csproj index c113b56..3fec19b 100644 --- a/Memphis.Jobs/Memphis.Jobs.csproj +++ b/Memphis.Jobs.ATC/Memphis.Jobs.ATC.csproj @@ -1,20 +1,19 @@ - + net8.0 enable enable - dotnet-Memphis.Jobs-d603f21a-8beb-441b-8827-46b910db709d + dotnet-Memphis.Jobs.ATC-f8559c82-f585-4241-9e8e-62130141bd5d + - + - - - - + + diff --git a/Memphis.Jobs/Program.cs b/Memphis.Jobs.ATC/Program.cs similarity index 67% rename from Memphis.Jobs/Program.cs rename to Memphis.Jobs.ATC/Program.cs index 0bc5814..ef22f4c 100644 --- a/Memphis.Jobs/Program.cs +++ b/Memphis.Jobs.ATC/Program.cs @@ -1,5 +1,6 @@ using dotenv.net; -using Memphis.Jobs; +using Memphis.Jobs.ATC; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Serilog; using Serilog.Events; @@ -24,26 +25,12 @@ services.AddDbContext(options => { options.UseNpgsql(Environment.GetEnvironmentVariable("CONNECTION_STRING") ?? - throw new ArgumentException("CONNECTION_STRING env variable not found")); - }); + throw new ArgumentException("CONNECTION_STRING env variable not found")); + }, ServiceLifetime.Singleton); var redisHost = Environment.GetEnvironmentVariable("REDIS_HOST") ?? throw new ArgumentNullException("REDIS_HOST env variable not found"); services.AddSingleton(ConnectionMultiplexer.Connect(redisHost).GetDatabase()); - services.AddScoped(); + services.AddHostedService(); }) .Build(); - -using (var scope = host.Services.CreateScope()) -{ - using (SentrySdk.Init(o => - { - o.Dsn = Environment.GetEnvironmentVariable("SENTRY_DSN"); - o.TracesSampleRate = 1.0; - o.IsGlobalModeEnabled = true; - })) - { - var worker = scope.ServiceProvider.GetRequiredService(); - await worker.Start(); - await host.RunAsync(); - } -} +await host.RunAsync(); \ No newline at end of file diff --git a/Memphis.Jobs/Properties/launchSettings.json b/Memphis.Jobs.ATC/Properties/launchSettings.json similarity index 90% rename from Memphis.Jobs/Properties/launchSettings.json rename to Memphis.Jobs.ATC/Properties/launchSettings.json index 6b361b7..ec42807 100644 --- a/Memphis.Jobs/Properties/launchSettings.json +++ b/Memphis.Jobs.ATC/Properties/launchSettings.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { - "Memphis.Jobs": { + "Memphis.Jobs.ATC": { "commandName": "Project", "dotnetRunMessages": true, "environmentVariables": { diff --git a/Memphis.Jobs.ATC/Worker.cs b/Memphis.Jobs.ATC/Worker.cs new file mode 100644 index 0000000..d592554 --- /dev/null +++ b/Memphis.Jobs.ATC/Worker.cs @@ -0,0 +1,258 @@ +using Discord; +using Discord.Webhook; +using Memphis.Shared.Data; +using Memphis.Shared.Datafeed; +using Memphis.Shared.Models; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace Memphis.Jobs.ATC +{ + public class Worker : BackgroundService + { + private readonly DatabaseContext _context; + private readonly IDatabase _redis; + private readonly DiscordWebhookClient _onlineWebhook; + private readonly DiscordWebhookClient _staffWebhook; + private readonly ILogger _logger; + + public Worker(DatabaseContext context, IDatabase redis, ILogger logger) + { + _context = context; + _redis = redis; + _onlineWebhook = new DiscordWebhookClient(Environment.GetEnvironmentVariable("ONLINE_DISCORD_WEBHOOK") ?? + throw new ArgumentNullException("ONLINE_DISCORD_WEBHOOK env variable not found")); + _staffWebhook = new DiscordWebhookClient(Environment.GetEnvironmentVariable("STAFF_DISCORD_WEBHOOK") ?? + throw new ArgumentNullException("STAFF_DISCORD_WEBHOOK env variable not found")); + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // Wait 1 second before starting + await Task.Delay(1000, stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("ATC job running at: {time}", DateTimeOffset.UtcNow); + + var added = 0; + var updated = 0; + var removed = 0; + + var redisDatafeed = await _redis.StringGetAsync("datafeed"); + if (!redisDatafeed.HasValue) + { + _logger.LogError("Failed to fetch datafeed data"); + await Task.Delay(int.Parse(Environment.GetEnvironmentVariable("DELAY") ?? "10000"), stoppingToken); + continue; + } + var datafeed = JsonConvert.DeserializeObject(redisDatafeed!); + if (datafeed is null) + { + _logger.LogError("Failed to deserialize datafeed data"); + await Task.Delay(int.Parse(Environment.GetEnvironmentVariable("DELAY") ?? "10000"), stoppingToken); + continue; + } + + var facilityIdentifiers = await _context.Facilities.Select(x => x.Identifier).ToListAsync(); + var memphisAtc = new List(); + foreach (var entry in facilityIdentifiers) + { + memphisAtc.AddRange(datafeed.Controllers.Where(x => x.Callsign.StartsWith(entry))); + } + + // Handle updating existing sessions and adding new ones + foreach (var entry in memphisAtc) + { + var existingSession = await _context.Sessions.Include(x => x.User) + .Where(x => x.User.Id == entry.Cid) + .Where(x => x.Callsign == entry.Callsign) + .Where(x => x.Frequency == entry.Frequency) + .Where(x => x.Start == entry.LogonTime) + .Where(x => x.Duration == TimeSpan.Zero) + .FirstOrDefaultAsync(); + if (existingSession == null) + { + var user = await _context.Users.FindAsync(entry.Cid); + if (user == null) + { + _logger.LogError("Failed to find user {UserId}", entry.Cid); + var embedNotFound = new EmbedBuilder + { + Title = $"{entry.Callsign} is online but not on roster!", + Fields = + { + new EmbedFieldBuilder + { + Name = "Name", + Value = entry.Name, + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Cid", + Value = entry.Cid, + IsInline = true + } + }, + Color = Color.Red, + Timestamp = DateTimeOffset.UtcNow + }; + await _staffWebhook.SendMessageAsync("", false, [embedNotFound.Build()]); + continue; + } + + await _context.Sessions.AddAsync(new Session + { + User = user, + Name = entry.Name, + Callsign = entry.Callsign, + Frequency = entry.Frequency, + Start = entry.LogonTime, + End = DateTimeOffset.UtcNow + }); + var embed = new EmbedBuilder + { + Title = $"{entry.Callsign} is online!", + Fields = + { + new EmbedFieldBuilder + { + Name = "Name", + Value = entry.Name, + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Cid", + Value = entry.Cid, + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Frequency", + Value = entry.Frequency, + IsInline = true + } + }, + Color = Color.Green, + Timestamp = DateTimeOffset.UtcNow + }; + await _onlineWebhook.SendMessageAsync("", false, [embed.Build()]); + added++; + } + else + { + existingSession.End = DateTimeOffset.UtcNow; + updated++; + } + await _context.SaveChangesAsync(); + } + + // Handle any sessions to remove + var onlineAtc = await _context.Sessions.Include(x => x.User).Where(x => x.Duration == TimeSpan.Zero).ToListAsync(); + foreach (var entry in onlineAtc) + { + if (!memphisAtc.Any(x => x.Callsign == entry.Callsign && x.Cid == entry.User.Id && + x.LogonTime == entry.Start && x.Frequency == entry.Frequency)) + { + if ((DateTimeOffset.UtcNow - entry.End).TotalSeconds < 45) + { + // Only end a session if it has been offline for 45 seconds (3 datafeed refreshes) + continue; + } + + entry.Duration = entry.End - entry.Start; + _context.Sessions.Update(entry); + + if (!await _context.Hours.AnyAsync(x => x.User == entry.User && x.Year == DateTime.UtcNow.Year && x.Month == DateTime.UtcNow.Month)) + { + await _context.Hours.AddAsync(new Hours + { + User = entry.User, + Year = DateTime.UtcNow.Year, + Month = DateTime.UtcNow.Month, + DeliveryHours = 0, + GroundHours = 0, + TowerHours = 0, + ApproachHours = 0, + CenterHours = 0 + }); + await _context.SaveChangesAsync(); + } + var hours = await _context.Hours + .Where(x => x.User == entry.User) + .Where(x => x.Year == DateTime.UtcNow.Year) + .Where(x => x.Year == DateTime.UtcNow.Year) + .FirstOrDefaultAsync(); + if (hours == null) + { + _logger.LogError("Failed to find hours for user {UserId}", entry.User.Id); + continue; + } + + if (entry.Callsign.EndsWith("DEL")) + { + hours.DeliveryHours += entry.Duration.TotalHours; + } + else if (entry.Callsign.EndsWith("GND")) + { + hours.GroundHours += entry.Duration.TotalHours; + } + else if (entry.Callsign.EndsWith("TWR")) + { + hours.TowerHours += entry.Duration.TotalHours; + } + else if (entry.Callsign.EndsWith("APP") || entry.Callsign.EndsWith("DEP")) + { + hours.ApproachHours += entry.Duration.TotalHours; + } + else if (entry.Callsign.EndsWith("CTR")) + { + hours.CenterHours += entry.Duration.TotalHours; + } + await _context.SaveChangesAsync(); + removed++; + var embed = new EmbedBuilder + { + Title = $"{entry.Callsign} is offline!", + Fields = + { + new EmbedFieldBuilder + { + Name = "Name", + Value = entry.Name, + IsInline = true, + }, + new EmbedFieldBuilder + { + Name = "Cid", + Value = entry.User.Id, + IsInline = true + }, + new EmbedFieldBuilder + { + Name = "Duration", + Value = $"{Math.Round(entry.Duration.TotalHours, 2)} hours", + IsInline = true + } + }, + Color = Color.Red, + Timestamp = DateTimeOffset.UtcNow + }; + await _onlineWebhook.SendMessageAsync("", false, [embed.Build()]); + } + } + + _logger.LogInformation("Added {added} sessions", added); + _logger.LogInformation("Updated {updated} sessions", updated); + _logger.LogInformation("Removed {removed} sessions", removed); + _logger.LogInformation("ATC job completed at: {time}", DateTimeOffset.UtcNow); + var delay = int.Parse(Environment.GetEnvironmentVariable("DELAY") ?? "10000"); + await Task.Delay(delay, stoppingToken); + } + } + } +} diff --git a/Memphis.Jobs.Airports/Dockerfile b/Memphis.Jobs.Airports/Dockerfile new file mode 100644 index 0000000..6da73f3 --- /dev/null +++ b/Memphis.Jobs.Airports/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.Airports/Memphis.Jobs.Airports.csproj", "Memphis.Jobs.Airports/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.Airports/Memphis.Jobs.Airports.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.Airports" +RUN dotnet build "./Memphis.Jobs.Airports.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.Airports.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.Airports.dll"] \ No newline at end of file diff --git a/Memphis.Jobs.Airports/Memphis.Jobs.Airports.csproj b/Memphis.Jobs.Airports/Memphis.Jobs.Airports.csproj new file mode 100644 index 0000000..ebe5fce --- /dev/null +++ b/Memphis.Jobs.Airports/Memphis.Jobs.Airports.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + dotnet-Memphis.Jobs.Airports-2f7f844d-3837-4d29-a94f-f6c3fdef8394 + + + + + + + + + + + + + + + + diff --git a/Memphis.Jobs.Airports/Program.cs b/Memphis.Jobs.Airports/Program.cs new file mode 100644 index 0000000..fb0481d --- /dev/null +++ b/Memphis.Jobs.Airports/Program.cs @@ -0,0 +1,7 @@ +using Memphis.Jobs.Airports; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/Memphis.Jobs.Airports/Properties/launchSettings.json b/Memphis.Jobs.Airports/Properties/launchSettings.json new file mode 100644 index 0000000..d2f7922 --- /dev/null +++ b/Memphis.Jobs.Airports/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Memphis.Jobs.Airports": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Memphis.Jobs.Airports/Worker.cs b/Memphis.Jobs.Airports/Worker.cs new file mode 100644 index 0000000..a1ed513 --- /dev/null +++ b/Memphis.Jobs.Airports/Worker.cs @@ -0,0 +1,24 @@ +namespace Memphis.Jobs.Airports +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/Memphis.Jobs.Datafeed/Dockerfile b/Memphis.Jobs.Datafeed/Dockerfile new file mode 100644 index 0000000..a161b63 --- /dev/null +++ b/Memphis.Jobs.Datafeed/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.Datafeed/Memphis.Jobs.Datafeed.csproj", "Memphis.Jobs.Datafeed/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.Datafeed/Memphis.Jobs.Datafeed.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.Datafeed" +RUN dotnet build "./Memphis.Jobs.Datafeed.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.Datafeed.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.Datafeed.dll"] \ No newline at end of file diff --git a/Memphis.Jobs.Datafeed/Memphis.Jobs.Datafeed.csproj b/Memphis.Jobs.Datafeed/Memphis.Jobs.Datafeed.csproj new file mode 100644 index 0000000..884d580 --- /dev/null +++ b/Memphis.Jobs.Datafeed/Memphis.Jobs.Datafeed.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + dotnet-Memphis.Jobs.Datafeed-9fa88bbc-eb12-4273-ae3a-5e23d3716694 + + + + + + + + + + + + + + + diff --git a/Memphis.Jobs.Datafeed/Program.cs b/Memphis.Jobs.Datafeed/Program.cs new file mode 100644 index 0000000..6d3ba8e --- /dev/null +++ b/Memphis.Jobs.Datafeed/Program.cs @@ -0,0 +1,30 @@ +using dotenv.net; +using Memphis.Jobs.Datafeed; +using Serilog; +using Serilog.Events; +using StackExchange.Redis; + + +DotEnv.Load(); + +var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(options => + { + options.ClearProviders(); + var logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); + options.AddSerilog(logger, dispose: true); + }) + .ConfigureServices(services => + { + var redisHost = Environment.GetEnvironmentVariable("REDIS_HOST") ?? + throw new ArgumentNullException("REDIS_HOST env variable not found"); + services.AddSingleton(ConnectionMultiplexer.Connect(redisHost).GetDatabase()); + services.AddHostedService(); + }) + .Build(); +await host.RunAsync(); \ No newline at end of file diff --git a/Memphis.Jobs.Datafeed/Properties/launchSettings.json b/Memphis.Jobs.Datafeed/Properties/launchSettings.json new file mode 100644 index 0000000..f5b74fd --- /dev/null +++ b/Memphis.Jobs.Datafeed/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Memphis.Jobs.Datafeed": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Memphis.Jobs.Datafeed/Worker.cs b/Memphis.Jobs.Datafeed/Worker.cs new file mode 100644 index 0000000..18ed5a2 --- /dev/null +++ b/Memphis.Jobs.Datafeed/Worker.cs @@ -0,0 +1,58 @@ +using Memphis.Shared.Status; +using Newtonsoft.Json; +using StackExchange.Redis; +using System.Net.Http.Json; + +namespace Memphis.Jobs.Datafeed; + +public class Worker : BackgroundService +{ + private readonly IDatabase _redis; + private readonly ILogger _logger; + + public Worker(IDatabase redis, ILogger logger) + { + _redis = redis; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // Wait 1 second before starting + await Task.Delay(1000, stoppingToken); + + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("Datafeed job running at: {time}", DateTimeOffset.UtcNow); + + using var client = new HttpClient(); + var statusUrl = Environment.GetEnvironmentVariable("STATUS_URL") ?? + throw new ArgumentNullException("STATUS_URL env variable not found"); + var response = await client.GetFromJsonAsync(statusUrl, stoppingToken); + + if (response is null) + { + _logger.LogError("Failed to fetch status data"); + await Task.Delay(10000, stoppingToken); + continue; + } + + var dataUrl = response.Data.V3.First(); + var data = await client.GetFromJsonAsync(dataUrl, stoppingToken); + + if (data is null) + { + _logger.LogError("Failed to fetch datafeed data"); + await Task.Delay(10000, stoppingToken); + continue; + } + + var dataJson = JsonConvert.SerializeObject(data); + await _redis.StringSetAsync("datafeed", dataJson, TimeSpan.FromSeconds(20)); + _logger.LogInformation("Datafeed data saved to redis"); + + var delay = int.Parse(Environment.GetEnvironmentVariable("DELAY") ?? "10000"); + await Task.Delay(delay, stoppingToken); + } + } +} diff --git a/Memphis.Jobs.Events/Dockerfile b/Memphis.Jobs.Events/Dockerfile new file mode 100644 index 0000000..ac22379 --- /dev/null +++ b/Memphis.Jobs.Events/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.Events/Memphis.Jobs.Events.csproj", "Memphis.Jobs.Events/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.Events/Memphis.Jobs.Events.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.Events" +RUN dotnet build "./Memphis.Jobs.Events.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.Events.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.Events.dll"] \ No newline at end of file diff --git a/Memphis.Jobs.Events/Memphis.Jobs.Events.csproj b/Memphis.Jobs.Events/Memphis.Jobs.Events.csproj new file mode 100644 index 0000000..89dc14b --- /dev/null +++ b/Memphis.Jobs.Events/Memphis.Jobs.Events.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + dotnet-Memphis.Jobs.Events-a0fdbcdb-822b-4009-84b5-88c75173fd07 + + + + + + + + + + + + + + + diff --git a/Memphis.Jobs.Events/Program.cs b/Memphis.Jobs.Events/Program.cs new file mode 100644 index 0000000..968944f --- /dev/null +++ b/Memphis.Jobs.Events/Program.cs @@ -0,0 +1,7 @@ +using Memphis.Jobs.Events; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/Memphis.Jobs.Events/Properties/launchSettings.json b/Memphis.Jobs.Events/Properties/launchSettings.json new file mode 100644 index 0000000..9ceb4d7 --- /dev/null +++ b/Memphis.Jobs.Events/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Memphis.Jobs.Events": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Memphis.Jobs.Events/Worker.cs b/Memphis.Jobs.Events/Worker.cs new file mode 100644 index 0000000..31c48db --- /dev/null +++ b/Memphis.Jobs.Events/Worker.cs @@ -0,0 +1,24 @@ +namespace Memphis.Jobs.Events +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/Memphis.Jobs.Roster/Dockerfile b/Memphis.Jobs.Roster/Dockerfile new file mode 100644 index 0000000..851d489 --- /dev/null +++ b/Memphis.Jobs.Roster/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.Roster/Memphis.Jobs.Roster.csproj", "Memphis.Jobs.Roster/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.Roster/Memphis.Jobs.Roster.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.Roster" +RUN dotnet build "./Memphis.Jobs.Roster.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.Roster.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.Roster.dll"] \ No newline at end of file diff --git a/Memphis.Jobs.Roster/Memphis.Jobs.Roster.csproj b/Memphis.Jobs.Roster/Memphis.Jobs.Roster.csproj new file mode 100644 index 0000000..ce4522c --- /dev/null +++ b/Memphis.Jobs.Roster/Memphis.Jobs.Roster.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + dotnet-Memphis.Jobs.Roster-861e36bd-684f-4819-b4e2-6ec1c0809437 + Linux + + + + + + + + + + + + + + + diff --git a/Memphis.Jobs.Roster/Program.cs b/Memphis.Jobs.Roster/Program.cs new file mode 100644 index 0000000..513eaa8 --- /dev/null +++ b/Memphis.Jobs.Roster/Program.cs @@ -0,0 +1,32 @@ +using dotenv.net; +using Memphis.Jobs.Roster; +using Memphis.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Serilog; +using Serilog.Events; + +DotEnv.Load(); + +var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(options => + { + options.ClearProviders(); + var logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); + options.AddSerilog(logger, dispose: true); + }) + .ConfigureServices(services => + { + services.AddDbContext(options => + { + options.UseNpgsql(Environment.GetEnvironmentVariable("CONNECTION_STRING") ?? + throw new ArgumentException("CONNECTION_STRING env variable not found")); + }, ServiceLifetime.Singleton); + services.AddHostedService(); + }) + .Build(); +await host.RunAsync(); \ No newline at end of file diff --git a/Memphis.Jobs.Roster/Properties/launchSettings.json b/Memphis.Jobs.Roster/Properties/launchSettings.json new file mode 100644 index 0000000..9a257dc --- /dev/null +++ b/Memphis.Jobs.Roster/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "Memphis.Jobs.Roster": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/Memphis.Jobs.Roster/Worker.cs b/Memphis.Jobs.Roster/Worker.cs new file mode 100644 index 0000000..ab9b025 --- /dev/null +++ b/Memphis.Jobs.Roster/Worker.cs @@ -0,0 +1,206 @@ +using Memphis.Shared.Data; +using Memphis.Shared.Enums; +using Memphis.Shared.Models; +using Memphis.Shared.Vatusa; +using Microsoft.EntityFrameworkCore; +using System.Net.Http.Json; + +namespace Memphis.Jobs.Roster +{ + public class Worker : BackgroundService + { + private readonly DatabaseContext _context; + private readonly ILogger _logger; + + public Worker(DatabaseContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + // Wait 1 second before starting + await Task.Delay(1000, stoppingToken); + + _logger.LogInformation("Roster job running at: {time}", DateTimeOffset.UtcNow); + + using var client = new HttpClient(); + var apiKey = Environment.GetEnvironmentVariable("VATUSA_API_KEY") ?? + throw new ArgumentNullException("VATUSA_API_KEY env variable not found"); + var facility = Environment.GetEnvironmentVariable("VATUSA_FACILITY") ?? + throw new ArgumentNullException("VATUSA_FACILITY env variable not found"); + var response = await client.GetFromJsonAsync($"https://api.vatusa.net/facility/{facility}/roster/both?apikey={apiKey}", + stoppingToken); + if (response == null) + { + _logger.LogError("Failed to retrieve roster data from VATUSA API"); + return; + } + + var apiUsers = response.Data; + var toRemove = new List(); + + _logger.LogInformation("Updating existing users..."); + var updatedCount = 0; + var existingUsers = await _context.Users.Include(x => x.Roles).ToListAsync(); + foreach (User user in existingUsers) + { + var wasUpdated = false; + + var apiUser = apiUsers.FirstOrDefault(x => x.Cid == user.Id); + if (apiUser == null) + { + toRemove.Add(user); + continue; + } + + // User switched from home controller to a visitor + if (!apiUser.Facility.Equals(facility, StringComparison.OrdinalIgnoreCase) && !user.Visitor) + { + user.Visitor = true; + user.VisitorFrom = apiUser.Facility; + } + + // User switched from visitor to home controller + if (apiUser.Facility.Equals(facility, StringComparison.OrdinalIgnoreCase) && user.Visitor) + { + user.Visitor = false; + user.VisitorFrom = null; + } + + if (user.FirstName != apiUser.FirstName) + { + user.FirstName = apiUser.FirstName; + wasUpdated = true; + } + + if (user.LastName != apiUser.LastName) + { + user.LastName = apiUser.LastName; + wasUpdated = true; + } + + if (user.Email != apiUser.Email) + { + user.Email = apiUser.Email; + wasUpdated = true; + } + + if (user.Rating != apiUser.Rating) + { + user.Rating = apiUser.Rating; + wasUpdated = true; + } + + foreach (var role in apiUser.Roles) + { + user.Roles ??= new List(); + var existingRole = user.Roles?.FirstOrDefault(x => x.NameShort == role.Role); + if (existingRole == null) + { + var zmeRole = await _context.Roles.FirstOrDefaultAsync(x => x.NameShort == role.Role); + if (zmeRole != null) + { + user.Roles?.Add(zmeRole); + wasUpdated = true; + } + else + { + _logger.LogWarning("Role {Role} not found in database, skipping", role.Role); + } + } + } + + if (wasUpdated) + { + _logger.LogInformation("{User} was updated", user.Id); + user.Updated = DateTime.UtcNow; + updatedCount++; + await _context.SaveChangesAsync(); + } + + apiUsers.Remove(apiUser); + } + _logger.LogInformation("Updated {Count} users", updatedCount); + + _logger.LogInformation("Adding new users..."); + var addedCount = 0; + foreach (RosterDto user in apiUsers) + { + await _context.Users.AddAsync(new User + { + Id = user.Cid, + FirstName = user.FirstName, + LastName = user.LastName, + Initials = await GetInitials(user.FirstName, user.LastName), + Email = user.Email, + Rating = user.Rating, + Joined = user.FacilityJoin, + Status = UserStatus.ACTIVE, + Visitor = !user.Facility.Equals(facility, StringComparison.OrdinalIgnoreCase), + VisitorFrom = !user.Facility.Equals(facility, StringComparison.OrdinalIgnoreCase) ? user.Facility : null, + Roles = new List(), + Created = DateTime.UtcNow, + Updated = DateTime.UtcNow + }); + await _context.SaveChangesAsync(); + addedCount++; + } + _logger.LogInformation("Added {Count} users", addedCount); + + _logger.LogInformation("Removing users..."); + var removedCount = 0; + foreach (User user in toRemove) + { + _context.Users.Remove(user); + await _context.SaveChangesAsync(); + removedCount++; + } + _logger.LogInformation("Removed {Count} users", removedCount); + + _logger.LogInformation("Roster job completed"); + + var delay = int.Parse(Environment.GetEnvironmentVariable("DELAY") ?? "3600000"); + await Task.Delay(delay, stoppingToken); + } + } + + protected async Task GetInitials(string firstName, string lastName) + { + var initials = $"{firstName[0]}{lastName[0]}"; + + var initialsExist = await _context.Users + .Where(x => x.Initials.Equals(initials)) + .ToListAsync(); + + if (!initialsExist.Any()) return initials; + + foreach (var letter in lastName) + { + initials = $"{firstName[0]}{letter.ToString().ToUpper()}"; + + var exists = await _context.Users + .Where(x => x.Initials.Equals(initials)) + .ToListAsync(); + + if (!exists.Any()) return initials.ToUpper(); + } + + foreach (var letter in firstName) + { + initials = $"{letter.ToString().ToUpper()}{lastName[0]}"; + + var exists = await _context.Users + .Where(x => x.Initials.Equals(initials)) + .ToListAsync(); + + if (!exists.Any()) return initials.ToUpper(); + } + + return string.Empty; + } + } +} diff --git a/Memphis.Jobs.SoloCerts/Dockerfile b/Memphis.Jobs.SoloCerts/Dockerfile new file mode 100644 index 0000000..61e5f07 --- /dev/null +++ b/Memphis.Jobs.SoloCerts/Dockerfile @@ -0,0 +1,24 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base +USER app +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Memphis.Jobs.SoloCerts/Memphis.Jobs.SoloCerts.csproj", "Memphis.Jobs.SoloCerts/"] +COPY ["Memphis.Shared/Memphis.Shared.csproj", "Memphis.Shared/"] +RUN dotnet restore "./Memphis.Jobs.SoloCerts/Memphis.Jobs.SoloCerts.csproj" +COPY . . +WORKDIR "/src/Memphis.Jobs.SoloCerts" +RUN dotnet build "./Memphis.Jobs.SoloCerts.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Memphis.Jobs.SoloCerts.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Memphis.Jobs.SoloCerts.dll"] \ No newline at end of file diff --git a/Memphis.Jobs.SoloCerts/Memphis.Jobs.SoloCerts.csproj b/Memphis.Jobs.SoloCerts/Memphis.Jobs.SoloCerts.csproj new file mode 100644 index 0000000..1f33853 --- /dev/null +++ b/Memphis.Jobs.SoloCerts/Memphis.Jobs.SoloCerts.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + dotnet-Memphis.Jobs.SoloCerts-e78dcf9e-cfa1-437f-8a2a-346947fbfb16 + Linux + + + + + + + + + + + + + + + diff --git a/Memphis.Jobs.SoloCerts/Program.cs b/Memphis.Jobs.SoloCerts/Program.cs new file mode 100644 index 0000000..13691f6 --- /dev/null +++ b/Memphis.Jobs.SoloCerts/Program.cs @@ -0,0 +1,7 @@ +using Memphis.Jobs.SoloCerts; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddHostedService(); + +var host = builder.Build(); +host.Run(); diff --git a/Memphis.Jobs.SoloCerts/Properties/launchSettings.json b/Memphis.Jobs.SoloCerts/Properties/launchSettings.json new file mode 100644 index 0000000..2c90ff9 --- /dev/null +++ b/Memphis.Jobs.SoloCerts/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "Memphis.Jobs.SoloCerts": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/Memphis.Jobs.SoloCerts/Worker.cs b/Memphis.Jobs.SoloCerts/Worker.cs new file mode 100644 index 0000000..f8b49f4 --- /dev/null +++ b/Memphis.Jobs.SoloCerts/Worker.cs @@ -0,0 +1,24 @@ +namespace Memphis.Jobs.SoloCerts +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + } + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/Memphis.Jobs/DatabaseContext.cs b/Memphis.Jobs/DatabaseContext.cs deleted file mode 100644 index cfec858..0000000 --- a/Memphis.Jobs/DatabaseContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Memphis.Shared.Models; -using Microsoft.EntityFrameworkCore; - -namespace Memphis.Jobs; - -public class DatabaseContext(DbContextOptions options) : DbContext(options) -{ - public required DbSet EmailLogs { get; set; } - public required DbSet Events { get; set; } - public required DbSet EventPositions { get; set; } - public required DbSet EventRegistrations { get; set; } - public required DbSet Hours { get; set; } - public required DbSet Notifications { get; set; } - public required DbSet OnlineControllers { get; set; } - public required DbSet Roles { get; set; } - public required DbSet Sessions { get; set; } - public required DbSet TrainingScheduleEntries { get; set; } - public required DbSet TrainingSchedules { get; set; } - public required DbSet TrainingTickets { get; set; } - public required DbSet TrainingTypes { get; set; } - public required DbSet Users { get; set; } - public required DbSet WebsiteLogs { get; set; } -} diff --git a/Memphis.Jobs/Jobs/Roster.cs b/Memphis.Jobs/Jobs/Roster.cs deleted file mode 100644 index bf2f014..0000000 --- a/Memphis.Jobs/Jobs/Roster.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Memphis.Jobs.Dtos; -using Memphis.Shared.Enums; -using Memphis.Shared.Models; -using Microsoft.EntityFrameworkCore; -using Quartz; -using System.Net.Http.Json; - -namespace Memphis.Jobs.Jobs; - -public class Roster : IJob -{ - private DatabaseContext? _context; - private ILogger? _logger; - - public async Task Execute(IJobExecutionContext context) - { - _context = (DatabaseContext)context.Scheduler.Context["databaseContext"]; - _logger = (ILogger)context.Scheduler.Context["logger"]; - - var client = new HttpClient(); - var apiKey = Environment.GetEnvironmentVariable("VATUSA_API_KEY") ?? - throw new ArgumentNullException("VATUSA_API_KEY env variable not found"); - var facility = Environment.GetEnvironmentVariable("VATUSA_FACILITY") ?? - throw new ArgumentNullException("VATUSA_FACILITY env variable not found"); - var response = await client.GetFromJsonAsync($"https://api.vatusa.net/facility/{facility}/roster/both?apikey={apiKey}"); - if (response == null) - { - _logger.LogError("Failed to retrieve roster data from VATUSA API"); - return; - } - - var apiUsers = response.Data; - var toRemove = new List(); - - _logger.LogInformation("Updating existing users..."); - var updatedCount = 0; - var existingUsers = await _context.Users.Include(x => x.Roles).ToListAsync(); - foreach (User user in existingUsers) - { - _logger.LogInformation("Updating user {User}", user.Id); - var wasUpdated = false; - - var apiUser = apiUsers.FirstOrDefault(x => x.Cid == user.Id); - if (apiUser == null) - { - toRemove.Add(user); - continue; - } - - // User switched from home controller to a visitor - if (apiUser.Facility != facility && !user.Visitor) - { - user.Visitor = true; - user.VisitorFrom = apiUser.Facility; - } - - // User switched from visitor to home controller - if (apiUser.Facility == facility && user.Visitor) - { - user.Visitor = false; - user.VisitorFrom = null; - } - - if (user.FirstName != apiUser.FirstName) - { - user.FirstName = apiUser.FirstName; - wasUpdated = true; - } - - if (user.LastName != apiUser.LastName) - { - user.LastName = apiUser.LastName; - wasUpdated = true; - } - - if (user.Email != apiUser.Email) - { - user.Email = apiUser.Email; - wasUpdated = true; - } - - if (user.Rating != apiUser.Rating) - { - user.Rating = apiUser.Rating; - wasUpdated = true; - } - - foreach (var role in apiUser.Roles) - { - user.Roles ??= new List(); - var existingRole = user.Roles?.FirstOrDefault(x => x.Name == role.Role); - if (existingRole == null) - { - var zmeRole = await _context.Roles.FirstOrDefaultAsync(x => x.Name == role.Role); - if (zmeRole != null) - { - user.Roles?.Add(zmeRole); - wasUpdated = true; - } - else - { - _logger.LogWarning("Role {Role} not found in database", role.Role); - } - } - } - - if (wasUpdated) - { - user.Updated = DateTime.UtcNow; - updatedCount++; - await _context.SaveChangesAsync(); - } - - apiUsers.Remove(apiUser); - } - _logger.LogInformation("Updated {Count} users", updatedCount); - - _logger.LogInformation("Adding new users..."); - var addedCount = 0; - foreach (RosterDto user in apiUsers) - { - await _context.Users.AddAsync(new User - { - Id = user.Cid, - FirstName = user.FirstName, - LastName = user.LastName, - Initials = await GetInitials(user.FirstName, user.LastName), - Email = user.Email, - Rating = user.Rating, - Joined = user.FacilityJoin, - Status = UserStatus.ACTIVE, - Visitor = user.Facility != facility, - VisitorFrom = user.Facility != facility ? user.Facility : null, - Roles = new List(), - Created = DateTime.UtcNow, - Updated = DateTime.UtcNow - }); - await _context.SaveChangesAsync(); - addedCount++; - } - _logger.LogInformation("Added {Count} users", addedCount); - - _logger.LogInformation("Removing users..."); - var removedCount = 0; - foreach (User user in toRemove) - { - _context.Users.Remove(user); - await _context.SaveChangesAsync(); - removedCount++; - } - _logger.LogInformation("Removed {Count} users", removedCount); - - _logger.LogInformation("Roster job completed"); - } - - public async Task GetInitials(string firstName, string lastName) - { - var initials = $"{firstName[0]}{lastName[0]}"; - - var initialsExist = await _context.Users - .Where(x => x.Initials.Equals(initials)) - .ToListAsync(); - - if (!initialsExist.Any()) return initials; - - foreach (var letter in lastName) - { - initials = $"{firstName[0]}{letter.ToString().ToUpper()}"; - - var exists = await _context.Users - .Where(x => x.Initials.Equals(initials)) - .ToListAsync(); - - if (!exists.Any()) return initials.ToUpper(); - } - - foreach (var letter in firstName) - { - initials = $"{letter.ToString().ToUpper()}{lastName[0]}"; - - var exists = await _context.Users - .Where(x => x.Initials.Equals(initials)) - .ToListAsync(); - - if (!exists.Any()) return initials.ToUpper(); - } - - return string.Empty; - } -} diff --git a/Memphis.Jobs/Worker.cs b/Memphis.Jobs/Worker.cs deleted file mode 100644 index 2c41907..0000000 --- a/Memphis.Jobs/Worker.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Memphis.Jobs.Jobs; -using Quartz; -using Quartz.Impl; - -namespace Memphis.Jobs -{ - public class Worker - { - private readonly ILogger _logger; - private readonly DatabaseContext _context; - - public Worker(ILogger logger, DatabaseContext context) - { - _logger = logger; - _context = context; - } - - public async Task Start() - { - _logger.LogInformation("Starting jobs..."); - var factory = new StdSchedulerFactory(); - var scheduler = await factory.GetScheduler(); - - scheduler.Context["databaseContext"] = _context; - scheduler.Context["logger"] = _logger; - - var rosterJob = JobBuilder.Create() - .WithIdentity("roster", "jobs") - .Build(); - var rosterTrigger = TriggerBuilder.Create() - .WithIdentity("roster", "jobs") - .StartNow() - .WithSimpleSchedule(x => x - .WithInterval(TimeSpan.FromMinutes(10)) - .RepeatForever()) - .Build(); - await scheduler.ScheduleJob(rosterJob, rosterTrigger); - - await scheduler.Start(); - } - } -} diff --git a/Memphis.API/Data/DatabaseContext.cs b/Memphis.Shared/Data/DatabaseContext.cs similarity index 98% rename from Memphis.API/Data/DatabaseContext.cs rename to Memphis.Shared/Data/DatabaseContext.cs index f1389fc..e216761 100644 --- a/Memphis.API/Data/DatabaseContext.cs +++ b/Memphis.Shared/Data/DatabaseContext.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using File = Memphis.Shared.Models.File; -namespace Memphis.API.Data; +namespace Memphis.Shared.Data; public class DatabaseContext(DbContextOptions options) : DbContext(options) { @@ -12,6 +12,7 @@ public class DatabaseContext(DbContextOptions options) : DbCont public required DbSet Events { get; set; } public required DbSet EventPositions { get; set; } public required DbSet EventRegistrations { get; set; } + public required DbSet Facilities { get; set; } public required DbSet Feedback { get; set; } public required DbSet Files { get; set; } public required DbSet Hours { get; set; } diff --git a/Memphis.Shared/Datafeed/Atis.cs b/Memphis.Shared/Datafeed/Atis.cs new file mode 100644 index 0000000..e3b795e --- /dev/null +++ b/Memphis.Shared/Datafeed/Atis.cs @@ -0,0 +1,56 @@ +using Memphis.Shared.Enums; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Memphis.Shared.Datafeed; + +public class Atis +{ + [JsonProperty("cid")] + [JsonPropertyName("cid")] + public int Cid { get; set; } + + [JsonProperty("name")] + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonProperty("callsign")] + [JsonPropertyName("callsign")] + public required string Callsign { get; set; } + + [JsonProperty("frequency")] + [JsonPropertyName("frequency")] + public required string Frequency { get; set; } + + [JsonProperty("facility")] + [JsonPropertyName("facility")] + public required Facility Facility { get; set; } + + [JsonProperty("rating")] + [JsonPropertyName("rating")] + public required Rating Rating { get; set; } + + [JsonProperty("server")] + [JsonPropertyName("server")] + public required string Server { get; set; } + + [JsonProperty("visual_range")] + [JsonPropertyName("visual_range")] + public int VisualRange { get; set; } + + [JsonProperty("atis_code")] + [JsonPropertyName("atis_code")] + public string? AtisCode { get; set; } + + [JsonProperty("text_atis")] + [JsonPropertyName("text_atis")] + public required IList TextAtis { get; set; } + + [JsonProperty("last_updated")] + [JsonPropertyName("last_updated")] + public DateTimeOffset LastUpdated { get; set; } + + [JsonProperty("logon_time")] + [JsonPropertyName("logon_time")] + public DateTimeOffset LogonTime { get; set; } +} diff --git a/Memphis.Shared/Datafeed/Controller.cs b/Memphis.Shared/Datafeed/Controller.cs new file mode 100644 index 0000000..287878e --- /dev/null +++ b/Memphis.Shared/Datafeed/Controller.cs @@ -0,0 +1,63 @@ +using Memphis.Shared.Enums; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Memphis.Shared.Datafeed; + +public enum Facility +{ + OBS, + FSS, + DEL, + GND, + TWR, + APP, + CTR +} + +public class Controller +{ + [JsonProperty("cid")] + [JsonPropertyName("cid")] + public int Cid { get; set; } + + [JsonProperty("name")] + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonProperty("callsign")] + [JsonPropertyName("callsign")] + public required string Callsign { get; set; } + + [JsonProperty("frequency")] + [JsonPropertyName("frequency")] + public required string Frequency { get; set; } + + [JsonProperty("facility")] + [JsonPropertyName("facility")] + public required Facility Facility { get; set; } + + [JsonProperty("rating")] + [JsonPropertyName("rating")] + public required Rating Rating { get; set; } + + [JsonProperty("server")] + [JsonPropertyName("server")] + public required string Server { get; set; } + + [JsonProperty("range")] + [JsonPropertyName("range")] + public int Range { get; set; } + + [JsonProperty("text_atis")] + [JsonPropertyName("text_atis")] + public IList? TextAtis { get; set; } + + [JsonProperty("last_updated")] + [JsonPropertyName("last_updated")] + public DateTimeOffset LastUpdated { get; set; } + + [JsonProperty("logon_time")] + [JsonPropertyName("logon_time")] + public DateTimeOffset LogonTime { get; set; } +} diff --git a/Memphis.Shared/Datafeed/Datafeed.cs b/Memphis.Shared/Datafeed/Datafeed.cs new file mode 100644 index 0000000..50241c0 --- /dev/null +++ b/Memphis.Shared/Datafeed/Datafeed.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Memphis.Shared.Datafeed; + +public class Datafeed +{ + [JsonProperty("pilots")] + [JsonPropertyName("pilots")] + public required IList Pilots { get; set; } + + [JsonProperty("controllers")] + [JsonPropertyName("controllers")] + public required IList Controllers { get; set; } + + [JsonProperty("atis")] + [JsonPropertyName("atis")] + public required IList Atis { get; set; } +} diff --git a/Memphis.Shared/Datafeed/Pilot.cs b/Memphis.Shared/Datafeed/Pilot.cs new file mode 100644 index 0000000..af93032 --- /dev/null +++ b/Memphis.Shared/Datafeed/Pilot.cs @@ -0,0 +1,162 @@ +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Memphis.Shared.Datafeed; + +public enum PilotRating +{ + NEW, + PPL, + IR = 3, + CMEL = 7, + ATPL = 15, + FI = 31, + FE = 63 +} + +public enum MilitaryRating +{ + M0, + M1, + M2 = 3, + M3 = 7, + M4 = 15 +} + +public class FlightPlan +{ + [JsonProperty("flight_rules")] + [JsonPropertyName("flight_rules")] + public required string FlightRules { get; set; } + + [JsonProperty("aircraft")] + [JsonPropertyName("aircraft")] + public required string Aircraft { get; set; } + + [JsonProperty("aircraft_faa")] + [JsonPropertyName("aircraft_faa")] + public required string AircraftFaa { get; set; } + + [JsonProperty("aircraft_short")] + [JsonPropertyName("aircraft_short")] + public required string AircraftShort { get; set; } + + [JsonProperty("departure")] + [JsonPropertyName("departure")] + public required string Departure { get; set; } + + [JsonProperty("arrival")] + [JsonPropertyName("arrival")] + public required string Arrival { get; set; } + + [JsonProperty("alternate")] + [JsonPropertyName("alternate")] + public required string Alternate { get; set; } + + [JsonProperty("cruise_tas")] + [JsonPropertyName("cruise_tas")] + public required string CruiseTas { get; set; } + + [JsonProperty("altitude")] + [JsonPropertyName("altitude")] + public required string Altitude { get; set; } + + [JsonProperty("deptime")] + [JsonPropertyName("deptime")] + public required string DepTime { get; set; } + + [JsonProperty("enroute_time")] + [JsonPropertyName("enroute_time")] + public required string EnrouteTime { get; set; } + + [JsonProperty("fuel_time")] + [JsonPropertyName("fuel_time")] + public required string FuelTime { get; set; } + + [JsonProperty("remarks")] + [JsonPropertyName("remarks")] + public required string Remarks { get; set; } + + [JsonProperty("route")] + [JsonPropertyName("route")] + public required string Route { get; set; } + + [JsonProperty("revision_id")] + [JsonPropertyName("revision_id")] + public int RevisionId { get; set; } + + [JsonProperty("assigned_transponder")] + [JsonPropertyName("assigned_transponder")] + public int AssignedTransponder { get; set; } +} + +public class Pilot +{ + [JsonProperty("cid")] + [JsonPropertyName("cid")] + public int Cid { get; set; } + + [JsonProperty("name")] + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonProperty("callsign")] + [JsonPropertyName("callsign")] + public required string Callsign { get; set; } + + [JsonProperty("server")] + [JsonPropertyName("server")] + public required string Server { get; set; } + + [JsonProperty("pilot_rating")] + [JsonPropertyName("pilot_rating")] + public required PilotRating PilotRating { get; set; } + + [JsonProperty("military_rating")] + [JsonPropertyName("military_rating")] + public required MilitaryRating MilitaryRating { get; set; } + + [JsonProperty("latitude")] + [JsonPropertyName("latitude")] + public double Latitude { get; set; } + + [JsonProperty("longitude")] + [JsonPropertyName("longitude")] + public double Longitude { get; set; } + + [JsonProperty("altitude")] + [JsonPropertyName("altitude")] + public int Altitude { get; set; } + + [JsonProperty("groundspeed")] + [JsonPropertyName("groundspeed")] + public int GroundSpeed { get; set; } + + [JsonProperty("transponder")] + [JsonPropertyName("transponder")] + public required string Transponder { get; set; } + + [JsonProperty("heading")] + [JsonPropertyName("heading")] + public int Heading { get; set; } + + [JsonProperty("qnh_i_hg")] + [JsonPropertyName("qnh_i_hg")] + public double QnhIhg { get; set; } + + [JsonProperty("qnh_mb")] + [JsonPropertyName("qnh_mb")] + public int QnhMb { get; set; } + + [JsonProperty("flight_plan")] + [JsonPropertyName("flight_plan")] + public FlightPlan? FlightPlan { get; set; } + + [JsonProperty("logon_time")] + [JsonPropertyName("logon_time")] + public DateTimeOffset LogonTime { get; set; } + + [JsonProperty("last_updated")] + [JsonPropertyName("last_updated")] + public DateTimeOffset LastUpdated { get; set; } +} diff --git a/Memphis.Shared/Memphis.Shared.csproj b/Memphis.Shared/Memphis.Shared.csproj index e83e061..b50f6cb 100644 --- a/Memphis.Shared/Memphis.Shared.csproj +++ b/Memphis.Shared/Memphis.Shared.csproj @@ -7,9 +7,11 @@ - - + + + + diff --git a/Memphis.API/Migrations/20240506212724_RemoveFaq.Designer.cs b/Memphis.Shared/Migrations/20240611191303_Initial.Designer.cs similarity index 99% rename from Memphis.API/Migrations/20240506212724_RemoveFaq.Designer.cs rename to Memphis.Shared/Migrations/20240611191303_Initial.Designer.cs index 0c53eba..8a0c66e 100644 --- a/Memphis.API/Migrations/20240506212724_RemoveFaq.Designer.cs +++ b/Memphis.Shared/Migrations/20240611191303_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,11 +9,11 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20240506212724_RemoveFaq")] - partial class RemoveFaq + [Migration("20240611191303_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -703,7 +703,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) new { Id = 1, - LastUpdated = new DateTimeOffset(new DateTime(2024, 5, 6, 21, 27, 22, 388, DateTimeKind.Unspecified).AddTicks(591), new TimeSpan(0, 0, 0, 0, 0)), + LastUpdated = new DateTimeOffset(new DateTime(2024, 6, 11, 19, 13, 2, 458, DateTimeKind.Unspecified).AddTicks(8141), new TimeSpan(0, 0, 0, 0, 0)), RequiredHours = 3, VisitingOpen = true }); diff --git a/Memphis.API/Migrations/20240123030804_Initial.cs b/Memphis.Shared/Migrations/20240611191303_Initial.cs similarity index 97% rename from Memphis.API/Migrations/20240123030804_Initial.cs rename to Memphis.Shared/Migrations/20240611191303_Initial.cs index 0917659..53cc07a 100644 --- a/Memphis.API/Migrations/20240123030804_Initial.cs +++ b/Memphis.Shared/Migrations/20240611191303_Initial.cs @@ -6,7 +6,7 @@ #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { /// public partial class Initial : Migration @@ -72,23 +72,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_Events", x => x.Id); }); - migrationBuilder.CreateTable( - name: "Faq", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Question = table.Column(type: "text", nullable: false), - Answer = table.Column(type: "text", nullable: false), - Order = table.Column(type: "integer", nullable: false), - Created = table.Column(type: "timestamp with time zone", nullable: false), - Updated = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Faq", x => x.Id); - }); - migrationBuilder.CreateTable( name: "Files", columns: table => new @@ -114,10 +97,12 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Cid = table.Column(type: "integer", nullable: false), Name = table.Column(type: "text", nullable: false), + Rating = table.Column(type: "integer", nullable: false), Callsign = table.Column(type: "text", nullable: false), Frequency = table.Column(type: "text", nullable: false), - Duration = table.Column(type: "interval", nullable: false) + Duration = table.Column(type: "text", nullable: false) }, constraints: table => { @@ -661,7 +646,7 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.InsertData( table: "Settings", columns: new[] { "Id", "LastUpdated", "RequiredHours", "VisitingOpen" }, - values: new object[] { 1, new DateTimeOffset(new DateTime(2024, 1, 23, 3, 8, 4, 225, DateTimeKind.Unspecified).AddTicks(8735), new TimeSpan(0, 0, 0, 0, 0)), 3, true }); + values: new object[] { 1, new DateTimeOffset(new DateTime(2024, 6, 11, 19, 13, 2, 458, DateTimeKind.Unspecified).AddTicks(8141), new TimeSpan(0, 0, 0, 0, 0)), 3, true }); migrationBuilder.CreateIndex( name: "IX_Airports_Icao", @@ -924,9 +909,6 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "EventRegistrations"); - migrationBuilder.DropTable( - name: "Faq"); - migrationBuilder.DropTable( name: "Feedback"); diff --git a/Memphis.API/Migrations/20240413042558_OnlineControllers.Designer.cs b/Memphis.Shared/Migrations/20240615144943_Positions.Designer.cs similarity index 98% rename from Memphis.API/Migrations/20240413042558_OnlineControllers.Designer.cs rename to Memphis.Shared/Migrations/20240615144943_Positions.Designer.cs index 2d69dee..9ae97e5 100644 --- a/Memphis.API/Migrations/20240413042558_OnlineControllers.Designer.cs +++ b/Memphis.Shared/Migrations/20240615144943_Positions.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,18 +9,18 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20240413042558_OnlineControllers")] - partial class OnlineControllers + [Migration("20240615144943_Positions")] + partial class Positions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -257,36 +257,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("EventRegistrations"); }); - modelBuilder.Entity("Memphis.Shared.Models.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Answer") - .IsRequired() - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("Question") - .IsRequired() - .HasColumnType("text"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("Faq"); - }); - modelBuilder.Entity("Memphis.Shared.Models.Feedback", b => { b.Property("Id") @@ -467,6 +437,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Cid") + .HasColumnType("integer"); + b.Property("Duration") .IsRequired() .HasColumnType("text"); @@ -479,6 +452,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Rating") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("OnlineControllers"); @@ -542,6 +518,27 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Ots"); }); + modelBuilder.Entity("Memphis.Shared.Models.Position", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Positions"); + }); + modelBuilder.Entity("Memphis.Shared.Models.Role", b => { b.Property("Id") @@ -727,7 +724,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) new { Id = 1, - LastUpdated = new DateTimeOffset(new DateTime(2024, 4, 13, 4, 25, 56, 546, DateTimeKind.Unspecified).AddTicks(2855), new TimeSpan(0, 0, 0, 0, 0)), + LastUpdated = new DateTimeOffset(new DateTime(2024, 6, 15, 14, 49, 42, 277, DateTimeKind.Unspecified).AddTicks(4205), new TimeSpan(0, 0, 0, 0, 0)), RequiredHours = 3, VisitingOpen = true }); diff --git a/Memphis.API/Migrations/20240506212724_RemoveFaq.cs b/Memphis.Shared/Migrations/20240615144943_Positions.cs similarity index 56% rename from Memphis.API/Migrations/20240506212724_RemoveFaq.cs rename to Memphis.Shared/Migrations/20240615144943_Positions.cs index 74e7555..660ae1a 100644 --- a/Memphis.API/Migrations/20240506212724_RemoveFaq.cs +++ b/Memphis.Shared/Migrations/20240615144943_Positions.cs @@ -4,51 +4,48 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { /// - public partial class RemoveFaq : Migration + public partial class Positions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "Faq"); + migrationBuilder.CreateTable( + name: "Positions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Identifier = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Positions", x => x.Id); + }); migrationBuilder.UpdateData( table: "Settings", keyColumn: "Id", keyValue: 1, column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 5, 6, 21, 27, 22, 388, DateTimeKind.Unspecified).AddTicks(591), new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset(new DateTime(2024, 6, 15, 14, 49, 42, 277, DateTimeKind.Unspecified).AddTicks(4205), new TimeSpan(0, 0, 0, 0, 0))); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.CreateTable( - name: "Faq", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Answer = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp with time zone", nullable: false), - Order = table.Column(type: "integer", nullable: false), - Question = table.Column(type: "text", nullable: false), - Updated = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Faq", x => x.Id); - }); + migrationBuilder.DropTable( + name: "Positions"); migrationBuilder.UpdateData( table: "Settings", keyColumn: "Id", keyValue: 1, column: "LastUpdated", - value: new DateTimeOffset(new DateTime(2024, 4, 13, 14, 41, 56, 263, DateTimeKind.Unspecified).AddTicks(453), new TimeSpan(0, 0, 0, 0, 0))); + value: new DateTimeOffset(new DateTime(2024, 6, 11, 19, 13, 2, 458, DateTimeKind.Unspecified).AddTicks(8141), new TimeSpan(0, 0, 0, 0, 0))); } } } diff --git a/Memphis.API/Migrations/20240413144157_OnlineControllersStuff.Designer.cs b/Memphis.Shared/Migrations/20240615150925_Facilities.Designer.cs similarity index 98% rename from Memphis.API/Migrations/20240413144157_OnlineControllersStuff.Designer.cs rename to Memphis.Shared/Migrations/20240615150925_Facilities.Designer.cs index e11cb98..7637440 100644 --- a/Memphis.API/Migrations/20240413144157_OnlineControllersStuff.Designer.cs +++ b/Memphis.Shared/Migrations/20240615150925_Facilities.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,18 +9,18 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20240413144157_OnlineControllersStuff")] - partial class OnlineControllersStuff + [Migration("20240615150925_Facilities")] + partial class Facilities { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -257,7 +257,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("EventRegistrations"); }); - modelBuilder.Entity("Memphis.Shared.Models.Faq", b => + modelBuilder.Entity("Memphis.Shared.Models.Facility", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -265,26 +265,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Answer") + b.Property("Identifier") .IsRequired() .HasColumnType("text"); - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("Question") + b.Property("Name") .IsRequired() .HasColumnType("text"); - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - b.HasKey("Id"); - b.ToTable("Faq"); + b.ToTable("Facilities"); }); modelBuilder.Entity("Memphis.Shared.Models.Feedback", b => @@ -733,7 +724,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) new { Id = 1, - LastUpdated = new DateTimeOffset(new DateTime(2024, 4, 13, 14, 41, 56, 263, DateTimeKind.Unspecified).AddTicks(453), new TimeSpan(0, 0, 0, 0, 0)), + LastUpdated = new DateTimeOffset(new DateTime(2024, 6, 15, 15, 9, 24, 778, DateTimeKind.Unspecified).AddTicks(7325), new TimeSpan(0, 0, 0, 0, 0)), RequiredHours = 3, VisitingOpen = true }); diff --git a/Memphis.Shared/Migrations/20240615150925_Facilities.cs b/Memphis.Shared/Migrations/20240615150925_Facilities.cs new file mode 100644 index 0000000..378c1fe --- /dev/null +++ b/Memphis.Shared/Migrations/20240615150925_Facilities.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Memphis.Shared.Migrations +{ + /// + public partial class Facilities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Positions"); + + migrationBuilder.CreateTable( + name: "Facilities", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Identifier = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Facilities", x => x.Id); + }); + + migrationBuilder.UpdateData( + table: "Settings", + keyColumn: "Id", + keyValue: 1, + column: "LastUpdated", + value: new DateTimeOffset(new DateTime(2024, 6, 15, 15, 9, 24, 778, DateTimeKind.Unspecified).AddTicks(7325), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Facilities"); + + migrationBuilder.CreateTable( + name: "Positions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Identifier = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Positions", x => x.Id); + }); + + migrationBuilder.UpdateData( + table: "Settings", + keyColumn: "Id", + keyValue: 1, + column: "LastUpdated", + value: new DateTimeOffset(new DateTime(2024, 6, 15, 14, 49, 42, 277, DateTimeKind.Unspecified).AddTicks(4205), new TimeSpan(0, 0, 0, 0, 0))); + } + } +} diff --git a/Memphis.API/Migrations/20240123030804_Initial.Designer.cs b/Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.Designer.cs similarity index 98% rename from Memphis.API/Migrations/20240123030804_Initial.Designer.cs rename to Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.Designer.cs index 1344606..5efff5a 100644 --- a/Memphis.API/Migrations/20240123030804_Initial.Designer.cs +++ b/Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,18 +9,18 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20240123030804_Initial")] - partial class Initial + [Migration("20240618154155_RemoveFacilityName")] + partial class RemoveFacilityName { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -257,7 +257,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("EventRegistrations"); }); - modelBuilder.Entity("Memphis.Shared.Models.Faq", b => + modelBuilder.Entity("Memphis.Shared.Models.Facility", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -265,26 +265,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Answer") + b.Property("Identifier") .IsRequired() .HasColumnType("text"); - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("Question") - .IsRequired() - .HasColumnType("text"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - b.HasKey("Id"); - b.ToTable("Faq"); + b.ToTable("Facilities"); }); modelBuilder.Entity("Memphis.Shared.Models.Feedback", b => @@ -467,8 +454,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); - b.Property("Duration") - .HasColumnType("interval"); + b.Property("Cid") + .HasColumnType("integer"); + + b.Property("Duration") + .IsRequired() + .HasColumnType("text"); b.Property("Frequency") .IsRequired() @@ -478,6 +469,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Rating") + .HasColumnType("integer"); + b.HasKey("Id"); b.ToTable("OnlineControllers"); @@ -726,7 +720,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) new { Id = 1, - LastUpdated = new DateTimeOffset(new DateTime(2024, 1, 23, 3, 8, 4, 225, DateTimeKind.Unspecified).AddTicks(8735), new TimeSpan(0, 0, 0, 0, 0)), + LastUpdated = new DateTimeOffset(new DateTime(2024, 6, 18, 15, 41, 53, 765, DateTimeKind.Unspecified).AddTicks(1700), new TimeSpan(0, 0, 0, 0, 0)), RequiredHours = 3, VisitingOpen = true }); diff --git a/Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.cs b/Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.cs new file mode 100644 index 0000000..e5cb65a --- /dev/null +++ b/Memphis.Shared/Migrations/20240618154155_RemoveFacilityName.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Memphis.Shared.Migrations +{ + /// + public partial class RemoveFacilityName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Name", + table: "Facilities"); + + migrationBuilder.UpdateData( + table: "Settings", + keyColumn: "Id", + keyValue: 1, + column: "LastUpdated", + value: new DateTimeOffset(new DateTime(2024, 6, 18, 15, 41, 53, 765, DateTimeKind.Unspecified).AddTicks(1700), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Name", + table: "Facilities", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.UpdateData( + table: "Settings", + keyColumn: "Id", + keyValue: 1, + column: "LastUpdated", + value: new DateTimeOffset(new DateTime(2024, 6, 15, 15, 9, 24, 778, DateTimeKind.Unspecified).AddTicks(7325), new TimeSpan(0, 0, 0, 0, 0))); + } + } +} diff --git a/Memphis.API/Migrations/DatabaseContextModelSnapshot.cs b/Memphis.Shared/Migrations/DatabaseContextModelSnapshot.cs similarity index 98% rename from Memphis.API/Migrations/DatabaseContextModelSnapshot.cs rename to Memphis.Shared/Migrations/DatabaseContextModelSnapshot.cs index ca184d9..d8e88e1 100644 --- a/Memphis.API/Migrations/DatabaseContextModelSnapshot.cs +++ b/Memphis.Shared/Migrations/DatabaseContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Memphis.API.Data; +using Memphis.Shared.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -8,7 +8,7 @@ #nullable disable -namespace Memphis.API.Migrations +namespace Memphis.Shared.Migrations { [DbContext(typeof(DatabaseContext))] partial class DatabaseContextModelSnapshot : ModelSnapshot @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -254,6 +254,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("EventRegistrations"); }); + modelBuilder.Entity("Memphis.Shared.Models.Facility", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Facilities"); + }); + modelBuilder.Entity("Memphis.Shared.Models.Feedback", b => { b.Property("Id") @@ -700,7 +717,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 1, - LastUpdated = new DateTimeOffset(new DateTime(2024, 5, 6, 21, 27, 22, 388, DateTimeKind.Unspecified).AddTicks(591), new TimeSpan(0, 0, 0, 0, 0)), + LastUpdated = new DateTimeOffset(new DateTime(2024, 6, 18, 15, 41, 53, 765, DateTimeKind.Unspecified).AddTicks(1700), new TimeSpan(0, 0, 0, 0, 0)), RequiredHours = 3, VisitingOpen = true }); diff --git a/Memphis.Shared/Models/Facility.cs b/Memphis.Shared/Models/Facility.cs new file mode 100644 index 0000000..2915ffe --- /dev/null +++ b/Memphis.Shared/Models/Facility.cs @@ -0,0 +1,7 @@ +namespace Memphis.Shared.Models; + +public class Facility +{ + public int Id { get; set; } + public required string Identifier { get; set; } +} diff --git a/Memphis.Shared/Status/Status.cs b/Memphis.Shared/Status/Status.cs new file mode 100644 index 0000000..63d1a02 --- /dev/null +++ b/Memphis.Shared/Status/Status.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Memphis.Shared.Status; + +public class Data +{ + [JsonProperty("v3")] + [JsonPropertyName("v3")] + public required IList V3 { get; set; } +} + +public class Status +{ + [JsonProperty("data")] + [JsonPropertyName("data")] + public required Data Data { get; set; } +} diff --git a/Memphis.Jobs/Dtos/RosterDto.cs b/Memphis.Shared/Vatusa/Roster.cs similarity index 92% rename from Memphis.Jobs/Dtos/RosterDto.cs rename to Memphis.Shared/Vatusa/Roster.cs index 3977af4..7c247a2 100644 --- a/Memphis.Jobs/Dtos/RosterDto.cs +++ b/Memphis.Shared/Vatusa/Roster.cs @@ -1,7 +1,7 @@ using Memphis.Shared.Enums; using System.Text.Json.Serialization; -namespace Memphis.Jobs.Dtos; +namespace Memphis.Shared.Vatusa; public class RoleDto { @@ -56,9 +56,6 @@ public class RosterDto [JsonPropertyName("roles")] public required IList Roles { get; set; } - [JsonPropertyName("last_promotion")] - public DateTimeOffset LastPromotion { get; set; } - [JsonPropertyName("membership")] public required string Membership { get; set; } } diff --git a/README.md b/README.md index d3c63f3..8f19af1 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# Memphis API \ No newline at end of file +# Memphis Web \ No newline at end of file diff --git a/Web.sln b/Web.sln new file mode 100644 index 0000000..8a8f78b --- /dev/null +++ b/Web.sln @@ -0,0 +1,88 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33815.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8CF65A81-01A0-4374-A576-E37AD5E740BE}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + .github\workflows\build-push.yml = .github\workflows\build-push.yml + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\create-tag.yaml = .github\workflows\create-tag.yaml + docker-compose.yaml = docker-compose.yaml + LICENSE.txt = LICENSE.txt + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memphis.API", "Memphis.API\Memphis.API.csproj", "{24C3E30E-A955-4CA4-92B7-2B9E1FB81701}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memphis.Shared", "Memphis.Shared\Memphis.Shared.csproj", "{3DEC6E30-E458-4509-8A4B-A7F2857E41E6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jobs", "Jobs", "{DB13A980-7287-43B0-AA3D-C805D96F0735}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.Datafeed", "Memphis.Jobs.Datafeed\Memphis.Jobs.Datafeed.csproj", "{6DB70010-EDB6-4D8D-A82A-A32F19EE4E39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.Roster", "Memphis.Jobs.Roster\Memphis.Jobs.Roster.csproj", "{7EAFA6A9-E876-4D13-A169-613428291837}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.ATC", "Memphis.Jobs.ATC\Memphis.Jobs.ATC.csproj", "{8FE1A491-0D3E-4963-965B-6F76C15DBAE5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.Airports", "Memphis.Jobs.Airports\Memphis.Jobs.Airports.csproj", "{6A4AA269-F12E-48F5-B245-5201D708870B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.Events", "Memphis.Jobs.Events\Memphis.Jobs.Events.csproj", "{E1CD8AD2-06B9-4FA6-9663-C450911CE312}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs.SoloCerts", "Memphis.Jobs.SoloCerts\Memphis.Jobs.SoloCerts.csproj", "{FD49F759-184C-45E1-BEC7-3E4D2DFC3039}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Release|Any CPU.Build.0 = Release|Any CPU + {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Release|Any CPU.Build.0 = Release|Any CPU + {6DB70010-EDB6-4D8D-A82A-A32F19EE4E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DB70010-EDB6-4D8D-A82A-A32F19EE4E39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DB70010-EDB6-4D8D-A82A-A32F19EE4E39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DB70010-EDB6-4D8D-A82A-A32F19EE4E39}.Release|Any CPU.Build.0 = Release|Any CPU + {7EAFA6A9-E876-4D13-A169-613428291837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EAFA6A9-E876-4D13-A169-613428291837}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EAFA6A9-E876-4D13-A169-613428291837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EAFA6A9-E876-4D13-A169-613428291837}.Release|Any CPU.Build.0 = Release|Any CPU + {8FE1A491-0D3E-4963-965B-6F76C15DBAE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FE1A491-0D3E-4963-965B-6F76C15DBAE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FE1A491-0D3E-4963-965B-6F76C15DBAE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FE1A491-0D3E-4963-965B-6F76C15DBAE5}.Release|Any CPU.Build.0 = Release|Any CPU + {6A4AA269-F12E-48F5-B245-5201D708870B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A4AA269-F12E-48F5-B245-5201D708870B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A4AA269-F12E-48F5-B245-5201D708870B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A4AA269-F12E-48F5-B245-5201D708870B}.Release|Any CPU.Build.0 = Release|Any CPU + {E1CD8AD2-06B9-4FA6-9663-C450911CE312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1CD8AD2-06B9-4FA6-9663-C450911CE312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1CD8AD2-06B9-4FA6-9663-C450911CE312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1CD8AD2-06B9-4FA6-9663-C450911CE312}.Release|Any CPU.Build.0 = Release|Any CPU + {FD49F759-184C-45E1-BEC7-3E4D2DFC3039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD49F759-184C-45E1-BEC7-3E4D2DFC3039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD49F759-184C-45E1-BEC7-3E4D2DFC3039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD49F759-184C-45E1-BEC7-3E4D2DFC3039}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6DB70010-EDB6-4D8D-A82A-A32F19EE4E39} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + {7EAFA6A9-E876-4D13-A169-613428291837} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + {8FE1A491-0D3E-4963-965B-6F76C15DBAE5} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + {6A4AA269-F12E-48F5-B245-5201D708870B} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + {E1CD8AD2-06B9-4FA6-9663-C450911CE312} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + {FD49F759-184C-45E1-BEC7-3E4D2DFC3039} = {DB13A980-7287-43B0-AA3D-C805D96F0735} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C4AE31B6-F2A4-48C3-9AE6-E766F8EDBD1F} + EndGlobalSection +EndGlobal diff --git a/api.sln b/api.sln deleted file mode 100644 index 936b21c..0000000 --- a/api.sln +++ /dev/null @@ -1,48 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33815.320 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8CF65A81-01A0-4374-A576-E37AD5E740BE}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - .github\workflows\build-push.yml = .github\workflows\build-push.yml - .github\workflows\build.yml = .github\workflows\build.yml - .github\workflows\create-tag.yaml = .github\workflows\create-tag.yaml - docker-compose.yaml = docker-compose.yaml - LICENSE.txt = LICENSE.txt - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memphis.API", "Memphis.API\Memphis.API.csproj", "{24C3E30E-A955-4CA4-92B7-2B9E1FB81701}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memphis.Shared", "Memphis.Shared\Memphis.Shared.csproj", "{3DEC6E30-E458-4509-8A4B-A7F2857E41E6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memphis.Jobs", "Memphis.Jobs\Memphis.Jobs.csproj", "{71EE4D25-B0EA-40D3-88C5-CE8C7DAD73D9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24C3E30E-A955-4CA4-92B7-2B9E1FB81701}.Release|Any CPU.Build.0 = Release|Any CPU - {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3DEC6E30-E458-4509-8A4B-A7F2857E41E6}.Release|Any CPU.Build.0 = Release|Any CPU - {71EE4D25-B0EA-40D3-88C5-CE8C7DAD73D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71EE4D25-B0EA-40D3-88C5-CE8C7DAD73D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71EE4D25-B0EA-40D3-88C5-CE8C7DAD73D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71EE4D25-B0EA-40D3-88C5-CE8C7DAD73D9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {C4AE31B6-F2A4-48C3-9AE6-E766F8EDBD1F} - EndGlobalSection -EndGlobal diff --git a/docker-compose.yaml b/docker-compose.yaml index cc98cbb..26d0d9c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3.8" - services: database: image: postgres:15 @@ -16,7 +14,7 @@ services: networks: - memphis redis: - image: redis:7.0.11 + image: redis:7 container_name: redis-memphis ports: - "6379:6379"