diff --git a/backend/api.test/EndpointTest.cs b/backend/api.test/EndpointTest.cs index c9fd67beb..e2b15d9a3 100644 --- a/backend/api.test/EndpointTest.cs +++ b/backend/api.test/EndpointTest.cs @@ -127,6 +127,7 @@ public void Dispose() } }; + var installationQuery = new CreateInstallationQuery { InstallationCode = installationCode, @@ -144,7 +145,8 @@ public void Dispose() { InstallationCode = installationCode, PlantCode = plantCode, - Name = deckName + Name = deckName, + LocalizationPose = testPose }; var areaQuery = new CreateAreaQuery @@ -383,7 +385,23 @@ public async Task AreaTest() W = 1 } }; + var testLocalizationPose = new Pose + { + Position = new Position + { + X = 1, + Y = 2, + Z = 3 + }, + Orientation = new Orientation + { + X = 0, + Y = 0, + Z = 0, + W = 1 + } + }; var installationQuery = new CreateInstallationQuery { InstallationCode = testInstallation, @@ -401,7 +419,8 @@ public async Task AreaTest() { InstallationCode = testInstallation, PlantCode = testPlant, - Name = testDeck + Name = testDeck, + LocalizationPose = testLocalizationPose }; var areaQuery = new CreateAreaQuery diff --git a/backend/api.test/EventHandlers/TestMissionScheduler.cs b/backend/api.test/EventHandlers/TestMissionScheduler.cs new file mode 100644 index 000000000..0fb99f4a9 --- /dev/null +++ b/backend/api.test/EventHandlers/TestMissionScheduler.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Api.Controllers; +using Api.Database.Context; +using Api.Database.Models; +using Api.EventHandlers; +using Api.Services; +using Api.Services.Models; +using Api.Test.Mocks; +using Api.Utilities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +#pragma warning disable CS1998 +namespace Api.Test.EventHandlers +{ + [Collection("Database collection")] + public class TestMissionScheduler : IDisposable + { + private static readonly Installation testInstallation = new() + { + InstallationCode = "test", + Name = "test test" + }; + private static readonly Plant testPlant = new() + { + PlantCode = "test", + Name = "test test", + Installation = testInstallation + }; + + private static MissionRun ScheduledMission => + new() + { + Name = "testMission", + MissionId = Guid.NewGuid().ToString(), + Status = MissionStatus.Pending, + DesiredStartTime = DateTimeOffset.Now, + Area = new Area + { + Deck = new Deck + { + Plant = testPlant, + Installation = testInstallation, + Name = "testDeck", + LocalizationPose = new LocalizationPose() + }, + Installation = testInstallation, + Plant = testPlant, + Name = "testArea", + MapMetadata = new MapMetadata() + { + MapName = "TestMap", + Boundary = new(), + TransformationMatrices = new() + }, + DefaultLocalizationPose = new Pose(), + SafePositions = new List() + }, + Map = new MapMetadata() + { + MapName = "TestMap", + Boundary = new(), + TransformationMatrices = new() + }, + InstallationCode = "testInstallation" + }; + + private readonly MissionScheduler _scheduledMissionEventHandler; + private readonly IMissionRunService _missionRunService; + private readonly IRobotService _robotService; + private readonly RobotControllerMock _robotControllerMock; + private readonly FlotillaDbContext _context; + + public TestMissionScheduler(DatabaseFixture fixture) + { + // Using Moq https://github.com/moq/moq4 + + var schedulerLogger = new Mock>().Object; + var missionLogger = new Mock>().Object; + + // Mock ScheduledMissionService: + _context = fixture.NewContext; + _missionRunService = new MissionRunService(_context, missionLogger); + _robotService = new RobotService(_context); + _robotControllerMock = new RobotControllerMock(); + + var mockServiceProvider = new Mock(); + + // Mock injection of MissionService: + mockServiceProvider + .Setup(p => p.GetService(typeof(IMissionRunService))) + .Returns(_missionRunService); + // Mock injection of RobotService: + mockServiceProvider + .Setup(p => p.GetService(typeof(IRobotService))) + .Returns(_robotService); + // Mock injection of Robot Controller + mockServiceProvider + .Setup(p => p.GetService(typeof(RobotController))) + .Returns(_robotControllerMock.Mock.Object); + // Mock injection of Database context + mockServiceProvider + .Setup(p => p.GetService(typeof(FlotillaDbContext))) + .Returns(_context); + + // Mock service injector + var mockScope = new Mock(); + mockScope.Setup(scope => scope.ServiceProvider).Returns(mockServiceProvider.Object); + var mockFactory = new Mock(); + mockFactory.Setup(f => f.CreateScope()).Returns(mockScope.Object); + + _scheduledMissionEventHandler = new MissionScheduler( + schedulerLogger, + mockFactory.Object + ); + } + + public void Dispose() + { + _context.Dispose(); + _scheduledMissionEventHandler.Dispose(); + GC.SuppressFinalize(this); + } + + /// + /// This function schedules a mission to be started immediately, waits for it to be executed and verifies + /// that the status went from to in the handling of the scheduled mission + /// + /// + /// + /// + private async void AssertExpectedStatusChange( + MissionStatus preStatus, + MissionStatus postStatus, + MissionRun missionRun + ) + { + // ARRANGE + + var cts = new CancellationTokenSource(); + + // Add Scheduled mission + await _missionRunService.Create(missionRun); + + _robotControllerMock.RobotServiceMock + .Setup(service => service.ReadById(missionRun.Robot.Id)) + .Returns(async () => missionRun.Robot); + + _robotControllerMock.MissionServiceMock + .Setup(service => service.ReadById(missionRun.Id)) + .Returns(async () => missionRun); + + // Assert start conditions + var preMission = await _missionRunService.ReadById(missionRun.Id); + Assert.NotNull(preMission); + Assert.Equal(preStatus, preMission!.Status); + + // ACT + + // Start / Stop eventhandler + await _scheduledMissionEventHandler.StartAsync(cts.Token); + await Task.Delay(3000); + await _scheduledMissionEventHandler.StopAsync(cts.Token); + + // ASSERT + + // Verify status change + var postMission = await _missionRunService.ReadById(missionRun.Id); + Assert.NotNull(postMission); + Assert.Equal(postStatus, postMission!.Status); + } + + [Fact] + // Test that if robot is busy, mission awaits available robot + public async void ScheduledMissionPendingIfRobotBusy() + { + var missionRun = ScheduledMission; + + // Get real robot to avoid error on robot model + var robot = (await _robotService.ReadAll()).First( + r => r is { Status: RobotStatus.Busy, Enabled: true } + ); + missionRun.Robot = robot; + + // Expect failed because robot does not exist + AssertExpectedStatusChange(MissionStatus.Pending, MissionStatus.Pending, missionRun); + } + + [Fact] + // Test that if robot is available, mission is started + public async void ScheduledMissionStartedIfRobotAvailable() + { + var missionRun = ScheduledMission; + + // Get real robot to avoid error on robot model + var robot = (await _robotService.ReadAll()).First( + r => r is { Status: RobotStatus.Available, Enabled: true } + ); + missionRun.Robot = robot; + + // Mock successful Start Mission: + _robotControllerMock.IsarServiceMock + .Setup(isar => isar.StartMission(robot, missionRun)) + .Returns( + async () => + new IsarMission( + new IsarStartMissionResponse + { + MissionId = "test", + Tasks = new List() + } + ) + ); + + // Expect failed because robot does not exist + AssertExpectedStatusChange(MissionStatus.Pending, MissionStatus.Ongoing, missionRun); + } + + [Fact] + // Test that if ISAR fails, mission is set to failed + public async void ScheduledMissionFailedIfIsarUnavailable() + { + var missionRun = ScheduledMission; + + // Get real robot to avoid error on robot model + var robot = (await _robotService.ReadAll()).First(); + robot.Enabled = true; + robot.Status = RobotStatus.Available; + await _robotService.Update(robot); + missionRun.Robot = robot; + + // Mock failing ISAR: + _robotControllerMock.IsarServiceMock + .Setup(isar => isar.StartMission(robot, missionRun)) + .Throws(new MissionException("ISAR Failed test message")); + + // Expect failed because robot does not exist + AssertExpectedStatusChange(MissionStatus.Pending, MissionStatus.Failed, missionRun); + } + } +} diff --git a/backend/api.test/Services/MissionService.cs b/backend/api.test/Services/MissionService.cs index 65fb7d0d1..fb8f327ad 100644 --- a/backend/api.test/Services/MissionService.cs +++ b/backend/api.test/Services/MissionService.cs @@ -72,7 +72,8 @@ public async Task Create() { Plant = testPlant, Installation = testInstallation, - Name = "testDeck" + Name = "testDeck", + LocalizationPose = new LocalizationPose() }, Installation = testInstallation, Plant = testPlant, diff --git a/backend/api/Controllers/DeckController.cs b/backend/api/Controllers/DeckController.cs index 596f79a32..637ea7820 100644 --- a/backend/api/Controllers/DeckController.cs +++ b/backend/api/Controllers/DeckController.cs @@ -13,6 +13,7 @@ public class DeckController : ControllerBase private readonly IDeckService _deckService; private readonly IInstallationService _installationService; private readonly IPlantService _plantService; + private readonly ILocalizationPoseService _localizationPoseService; private readonly IMapService _mapService; @@ -23,7 +24,8 @@ public DeckController( IMapService mapService, IDeckService deckService, IInstallationService installationService, - IPlantService plantService + IPlantService plantService, + ILocalizationPoseService localizationPoseService ) { _logger = logger; @@ -31,6 +33,7 @@ IPlantService plantService _deckService = deckService; _installationService = installationService; _plantService = plantService; + _localizationPoseService = localizationPoseService; } /// @@ -88,6 +91,60 @@ public async Task> GetDeckById([FromRoute] string id) } + /// + /// Add or update the localization pose to a deck + /// + /// + /// This query updates an existing deck with a new localization pose + /// + [HttpPut] + [Route("add-localization-pose/{deckId}")] + [Authorize(Roles = Role.Admin)] + [ProducesResponseType(typeof(Deck), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> AddLocalizationPoseToDeck([FromRoute] string deckId, [FromBody] Pose localizationPose) + { + _logger.LogInformation("Updating localization pose to an existing deck"); + try + { + + var existingDeck = await _deckService.ReadById(deckId); + if (existingDeck == null) + { + _logger.LogInformation("Could not find the deck"); + return BadRequest($"Deck already exists"); + } + + + if (existingDeck.LocalizationPose != null) + { + _logger.LogInformation("Removing old localization pose"); + LocalizationPose? oldLocalizationPose = await _localizationPoseService.Delete(existingDeck.LocalizationPose.Id); + } + + var newLocalizationPose = await _localizationPoseService.Create(localizationPose); + existingDeck.LocalizationPose = newLocalizationPose; + var updateDeck = await _deckService.Update(existingDeck); + _logger.LogInformation( + "Succesfully created new deck with id '{deckId}'", + updateDeck.Id + ); + return CreatedAtAction( + nameof(GetDeckById), + new { id = updateDeck.Id }, + updateDeck + ); + } + catch (Exception e) + { + _logger.LogError(e, "Error while adding a localization pose to deck"); + throw; + } + } + /// /// Add a new deck /// diff --git a/backend/api/Controllers/LocalizationPoseController.cs b/backend/api/Controllers/LocalizationPoseController.cs new file mode 100644 index 000000000..dfc49f656 --- /dev/null +++ b/backend/api/Controllers/LocalizationPoseController.cs @@ -0,0 +1,146 @@ +using Api.Controllers.Models; +using Api.Database.Models; +using Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers +{ + [ApiController] + [Route("localization-pose")] + public class LocalizationPoseController : ControllerBase + { + private readonly ILocalizationPoseService _localizationPoseService; + private readonly IInstallationService _installationService; + private readonly IPlantService _plantService; + + private readonly IMapService _mapService; + + private readonly ILogger _logger; + + public LocalizationPoseController( + ILogger logger, + IMapService mapService, + ILocalizationPoseService localizationPoseService, + IInstallationService installationService, + IPlantService plantService + ) + { + _logger = logger; + _mapService = mapService; + _localizationPoseService = localizationPoseService; + _installationService = installationService; + _plantService = plantService; + } + + /// + /// List all decks in the Flotilla database + /// + /// + /// This query gets all decks + /// + [HttpGet] + [Authorize(Roles = Role.Any)] + [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task>> GetLocalizationPoses() + { + try + { + var decks = await _localizationPoseService.ReadAll(); + return Ok(decks); + } + catch (Exception e) + { + _logger.LogError(e, "Error during GET of decks from database"); + throw; + } + } + + /// + /// Lookup localization pose by specified id. + /// + [HttpGet] + [Authorize(Roles = Role.Any)] + [Route("{id}")] + [ProducesResponseType(typeof(LocalizationPose), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetLocalizationPoseById([FromRoute] string id) + { + try + { + var localizationPose = await _localizationPoseService.ReadById(id); + if (localizationPose == null) + return NotFound($"Could not find localization pose with id {id}"); + return Ok(localizationPose); + } + catch (Exception e) + { + _logger.LogError(e, "Error during GET of localization pose from database"); + throw; + } + + } + + /// + /// Add a new localization pose + /// + /// + /// This query adds a new localization pose to the database + /// + [HttpPost] + [Authorize(Roles = Role.Admin)] + [ProducesResponseType(typeof(LocalizationPose), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> Create([FromBody] CreateLocalizationPoseQuery localizationPoseQuery) + { + _logger.LogInformation("Creating new localization pose"); + try + { + var newLocalizationPose = await _localizationPoseService.Create(localizationPoseQuery.Pose); + _logger.LogInformation( + "Succesfully created new localization pose with id '{deckId}'", + newLocalizationPose.Id + ); + return CreatedAtAction( + nameof(GetLocalizationPoseById), + new { id = newLocalizationPose.Id }, + newLocalizationPose + ); + } + catch (Exception e) + { + _logger.LogError(e, "Error while creating new localization pose"); + throw; + } + } + + /// + /// Deletes the localization pose with the specified id from the database. + /// + [HttpDelete] + [Authorize(Roles = Role.Admin)] + [Route("{id}")] + [ProducesResponseType(typeof(LocalizationPose), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> DeleteLocalizationPose([FromRoute] string id) + { + var localizationPose = await _localizationPoseService.Delete(id); + if (localizationPose is null) + return NotFound($"LocalizationPose with id {id} not found"); + return Ok(localizationPose); + } + } +} diff --git a/backend/api/Controllers/Models/AddLocalizationPoseToDeckQuery.cs b/backend/api/Controllers/Models/AddLocalizationPoseToDeckQuery.cs new file mode 100644 index 000000000..d40139d49 --- /dev/null +++ b/backend/api/Controllers/Models/AddLocalizationPoseToDeckQuery.cs @@ -0,0 +1,10 @@ +using Api.Database.Models; + +namespace Api.Controllers.Models +{ + public struct AddLocalizationPoseToDeckQuery + { + public string DeckId { get; set; } + public Pose LocalizationPose { get; set; } + } +} diff --git a/backend/api/Controllers/Models/CreateDeckQuery.cs b/backend/api/Controllers/Models/CreateDeckQuery.cs index 284e0584b..0d5ff7255 100644 --- a/backend/api/Controllers/Models/CreateDeckQuery.cs +++ b/backend/api/Controllers/Models/CreateDeckQuery.cs @@ -1,9 +1,12 @@ -namespace Api.Controllers.Models +using Api.Database.Models; + +namespace Api.Controllers.Models { public struct CreateDeckQuery { public string InstallationCode { get; set; } public string PlantCode { get; set; } public string Name { get; set; } + public Pose? LocalizationPose { get; set; } } } diff --git a/backend/api/Controllers/Models/CreateLocalizationPoseQuery.cs b/backend/api/Controllers/Models/CreateLocalizationPoseQuery.cs new file mode 100644 index 000000000..d7f13ba7d --- /dev/null +++ b/backend/api/Controllers/Models/CreateLocalizationPoseQuery.cs @@ -0,0 +1,9 @@ +using Api.Database.Models; + +namespace Api.Controllers.Models +{ + public struct CreateLocalizationPoseQuery + { + public Pose Pose { get; set; } + } +} diff --git a/backend/api/Database/Context/FlotillaDbContext.cs b/backend/api/Database/Context/FlotillaDbContext.cs index a49930231..1548ebdfd 100644 --- a/backend/api/Database/Context/FlotillaDbContext.cs +++ b/backend/api/Database/Context/FlotillaDbContext.cs @@ -17,6 +17,7 @@ public class FlotillaDbContext : DbContext public DbSet Areas => Set(); public DbSet Sources => Set(); public DbSet SafePositions => Set(); + public DbSet LocalizationPoses => Set(); public FlotillaDbContext(DbContextOptions options) : base(options) { } @@ -75,6 +76,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasOne(d => d.Plant).WithMany(); modelBuilder.Entity().HasOne(d => d.Installation).WithMany(); modelBuilder.Entity().HasOne(a => a.Installation).WithMany(); + modelBuilder.Entity().OwnsOne(s => s.Pose, poseBuilder => + { + poseBuilder.OwnsOne(pose => pose.Position); + poseBuilder.OwnsOne(pose => pose.Orientation); + }); modelBuilder.Entity().OwnsOne(s => s.Pose, poseBuilder => { diff --git a/backend/api/Database/Context/InitDb.cs b/backend/api/Database/Context/InitDb.cs index 2b2f0dc16..719ac3755 100644 --- a/backend/api/Database/Context/InitDb.cs +++ b/backend/api/Database/Context/InitDb.cs @@ -66,7 +66,8 @@ private static List GetDecks() Id = Guid.NewGuid().ToString(), Plant = plants[0], Installation = plants[0].Installation, - Name = "TestDeck" + Name = "TestDeck", + LocalizationPose = new LocalizationPose { } }; return new List(new Deck[] { deck1 }); diff --git a/backend/api/Database/Models/Area.cs b/backend/api/Database/Models/Area.cs index f9a775f3b..e731b643e 100644 --- a/backend/api/Database/Models/Area.cs +++ b/backend/api/Database/Models/Area.cs @@ -26,6 +26,8 @@ public class Area [Required] public Pose DefaultLocalizationPose { get; set; } + public LocalizationPose? LocalizationPose { get; set; } + public IList SafePositions { get; set; } } diff --git a/backend/api/Database/Models/Deck.cs b/backend/api/Database/Models/Deck.cs index 6411ab3ed..e9e9f5f1f 100644 --- a/backend/api/Database/Models/Deck.cs +++ b/backend/api/Database/Models/Deck.cs @@ -14,6 +14,8 @@ public class Deck public virtual Installation? Installation { get; set; } + public LocalizationPose? LocalizationPose { get; set; } + [Required] [MaxLength(200)] public string Name { get; set; } diff --git a/backend/api/Database/Models/LocalizationPose.cs b/backend/api/Database/Models/LocalizationPose.cs new file mode 100644 index 000000000..fde1c57f5 --- /dev/null +++ b/backend/api/Database/Models/LocalizationPose.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +#pragma warning disable CS8618 +namespace Api.Database.Models +{ + + public class LocalizationPose + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } + + public Pose Pose { get; set; } + + public LocalizationPose() + { + Pose = new Pose(); + } + + public LocalizationPose(Pose pose) + { + Pose = pose; + } + } + +} diff --git a/backend/api/Program.cs b/backend/api/Program.cs index 4bf01b061..513f55307 100644 --- a/backend/api/Program.cs +++ b/backend/api/Program.cs @@ -51,6 +51,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/backend/api/Services/DeckService.cs b/backend/api/Services/DeckService.cs index 8b49fd892..16fc4f14e 100644 --- a/backend/api/Services/DeckService.cs +++ b/backend/api/Services/DeckService.cs @@ -1,8 +1,10 @@ -using Api.Controllers.Models; +using System.Xml; +using Api.Controllers.Models; using Api.Database.Context; using Api.Database.Models; using Api.Utilities; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.ObjectPool; namespace Api.Services { @@ -41,12 +43,14 @@ public class DeckService : IDeckService private readonly FlotillaDbContext _context; private readonly IInstallationService _installationService; private readonly IPlantService _plantService; + private readonly ILocalizationPoseService _localizationPoseService; - public DeckService(FlotillaDbContext context, IInstallationService installationService, IPlantService plantService) + public DeckService(FlotillaDbContext context, IInstallationService installationService, IPlantService plantService, ILocalizationPoseService localizationPoseService) { _context = context; _installationService = installationService; _plantService = plantService; + _localizationPoseService = localizationPoseService; } public async Task> ReadAll() @@ -56,7 +60,7 @@ public async Task> ReadAll() private IQueryable GetDecks() { - return _context.Decks.Include(p => p.Plant).Include(i => i.Installation); + return _context.Decks.Include(p => p.Plant).Include(i => i.Installation).Include(l => l.LocalizationPose); } public async Task ReadById(string id) @@ -89,7 +93,7 @@ public async Task> ReadByInstallation(string installationCode) a.Plant.Id.Equals(plant.Id) && a.Installation.Id.Equals(installation.Id) && a.Name.ToLower().Equals(name.ToLower()) - ).Include(d => d.Plant).Include(i => i.Installation).FirstOrDefaultAsync(); + ).Include(d => d.Plant).Include(i => i.Installation).Include(l => l.LocalizationPose).FirstOrDefaultAsync(); } public async Task Create(CreateDeckQuery newDeckQuery) @@ -107,11 +111,18 @@ public async Task Create(CreateDeckQuery newDeckQuery) var deck = await ReadByInstallationAndPlantAndName(installation, plant, newDeckQuery.Name); if (deck == null) { + LocalizationPose? newLocalizationPose = null; + if (newDeckQuery.LocalizationPose != null) + { + newLocalizationPose = await _localizationPoseService.Create(newDeckQuery.LocalizationPose); + } + deck = new Deck { Name = newDeckQuery.Name, Installation = installation, - Plant = plant + Plant = plant, + LocalizationPose = newLocalizationPose }; await _context.Decks.AddAsync(deck); await _context.SaveChangesAsync(); diff --git a/backend/api/Services/LocalizationPoseService.cs b/backend/api/Services/LocalizationPoseService.cs new file mode 100644 index 000000000..cf1924d04 --- /dev/null +++ b/backend/api/Services/LocalizationPoseService.cs @@ -0,0 +1,88 @@ +using Api.Controllers.Models; +using Api.Database.Context; +using Api.Database.Models; +using Api.Utilities; +using Microsoft.EntityFrameworkCore; + +namespace Api.Services +{ + public interface ILocalizationPoseService + { + public abstract Task> ReadAll(); + + public abstract Task ReadById(string id); + + public abstract Task Create(Pose newLocalizationPose); + + public abstract Task Update(LocalizationPose localizationPose); + + public abstract Task Delete(string id); + + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Globalization", + "CA1309:Use ordinal StringComparison", + Justification = "EF Core refrains from translating string comparison overloads to SQL" + )] + public class LocalizationPoseService : ILocalizationPoseService + { + private readonly FlotillaDbContext _context; + + public LocalizationPoseService(FlotillaDbContext context) + { + _context = context; + + } + + public async Task> ReadAll() + { + return await GetLocalizationPoses().ToListAsync(); + } + + private IQueryable GetLocalizationPoses() + { + return _context.LocalizationPoses; + } + + public async Task ReadById(string id) + { + return await GetLocalizationPoses() + .FirstOrDefaultAsync(a => a.Id.Equals(id)); + } + + public async Task Create(Pose newLocalizationPose) + { + + var localizationPose = new LocalizationPose + { + Pose = newLocalizationPose + }; + await _context.LocalizationPoses.AddAsync(localizationPose); + await _context.SaveChangesAsync(); + return localizationPose!; + } + + public async Task Update(LocalizationPose localizationPose) + { + var entry = _context.Update(localizationPose); + await _context.SaveChangesAsync(); + return entry.Entity; + } + + public async Task Delete(string id) + { + var localizationPose = await GetLocalizationPoses() + .FirstOrDefaultAsync(ev => ev.Id.Equals(id)); + if (localizationPose is null) + { + return null; + } + + _context.LocalizationPoses.Remove(localizationPose); + await _context.SaveChangesAsync(); + + return localizationPose; + } + } +}