<repo_name>netososilva/car-pooling<file_sep>/CarPooling.Domain/Business/Groups.cs
using System.Collections.Generic;
using System.Linq;
using CarPooling.Domain.Models;

namespace CarPooling.Domain.Business
{
    public class Groups
    {
        private Dictionary<int, Group> WaitingGroups { get; set; }

        public Groups()
        {
            WaitingGroups = new Dictionary<int, Group>();
        }

        public void Clear()
        {
            this.WaitingGroups.Clear();
        }

        public void Add(Group group)
        {
            this.WaitingGroups.Add(group.Id, group);
        }

        public bool ExistsGroup(int groupId)
        {
            return this.WaitingGroups.ContainsKey(groupId);
        }

        public List<int> GetGroupsId()
        {
            return WaitingGroups.Keys.ToList();
        }

        public Group GetGroup(int groupId)
        {
            return WaitingGroups[groupId];
        }

        public void Remove(int groupId)
        {
            WaitingGroups.Remove(groupId);
        }
    }
}<file_sep>/CarPooling.Domain/Business/Cars.cs
using System.Collections.Generic;
using System.Linq;
using CarPooling.Domain.Models;

namespace CarPooling.Domain.Business
{
    public class Cars
    {
        private Dictionary<int, List<Car>> AvailableCars;

        public Cars()
        {
            AvailableCars = new Dictionary<int, List<Car>>();
        }

        public void UpdateCars(IEnumerable<Car> cars)
        {
            this.AvailableCars = cars.
                GroupBy(car => car.Seats).
                ToDictionary(car => car.Key, car => car.ToList());
        }

        public void Add(Car car)
        {
            if (!AvailableCars.ContainsKey(car.Seats))
                AvailableCars.Add(car.Seats, new List<Car>());

            AvailableCars[car.Seats].Add(car);
        }

        public bool IsAvailable(int seats)
        {
            if (!AvailableCars.ContainsKey(seats))
                return false;

            if (AvailableCars[seats].Any())
                return true;

            return false;
        }

        public Car GetCarBySeats(int seats)
        {
            return AvailableCars[seats].FirstOrDefault();
        }

        public void Remove(Car car)
        {
            AvailableCars[car.Seats].Remove(car);

            if (!AvailableCars[car.Seats].Any())
                AvailableCars.Remove(car.Seats);
        }
    }
}<file_sep>/CarPooling.Domain/Business/Journeys.cs
using System.Collections.Generic;
using CarPooling.Domain.Models;

namespace CarPooling.Domain.Business
{
    public class Journeys
    {
        public Dictionary<int, Journey> ActiveJourneys { get; set; }

        public Journeys()
        {
            ActiveJourneys = new Dictionary<int, Journey>();
        }

        public void Clear()
        {
            ActiveJourneys = new Dictionary<int, Journey>();
        }

        public Car DropOff(int groupId)
        {
            var journey = Locate(groupId);

            journey.DropOff(journey.Group);
            ActiveJourneys.Remove(groupId);

            return journey.Car;
        }

        public void CreateJourney(Car car, Group group)
        {
            var journey = new Journey
            {
                Car = car,
                Group = group
            };

            ActiveJourneys.Add(group.Id, journey);
        }

        public Journey Locate(int groupId)
        {
            return ActiveJourneys[groupId];
        }

        public bool ExistsJourney(int groupId)
        {
            return ActiveJourneys.ContainsKey(groupId);
        }
    }
}<file_sep>/CarPooling.Api/Controllers/CarsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using CarPooling.Api.Filters;
using CarPooling.Domain.Business;
using CarPooling.Domain.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace CarPooling.Api.Controllers
{
    public class CarsController : ControllerBase
    {
        private readonly Pooling carPooling = new Pooling();

        // PUT: cars
        [Route("cars")]
        [ServiceFilter(typeof(PutFilter))]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [Consumes("application/json")]
        public IActionResult Cars([FromBody] IEnumerable<Car> cars)
        {
            if (cars == null || cars.Count() == 0) return BadRequest();

            try
            {
                carPooling.UpdateCars(cars);

                return Ok();
            }
            catch (Exception ex)
            {
                return BadRequest(ex);
            }
        }

        // POST: journey
        [Route("journey")]
        [ServiceFilter(typeof(PostFilter))]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [Consumes("application/json")]
        public IActionResult Journey([FromBody] Group group)
        {
            if (group == null || group.Invalid) return BadRequest();

            try
            {
                carPooling.AlocateGroup(group);
                
                return Ok();
            }
            catch(Exception ex)
            {
                return BadRequest(ex);
            }
        }

        // POST: dropoff
        [Route("dropoff")]
        [ServiceFilter(typeof(PostFilter))]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [Consumes("application/x-www-form-urlencoded")]
        public IActionResult DropOff([FromForm] string ID)
        {
            if (string.IsNullOrWhiteSpace(ID)) return BadRequest();

            if (!int.TryParse(ID, out var groupId)) return BadRequest();

            try
            {
                if (!carPooling.ExistsGroup(groupId)) return NotFound();

                carPooling.DropOff(groupId);

                return Ok();
            }
            catch(Exception ex)
            {
                return BadRequest(ex);
            }
        }

        // POST: locate
        [Route("locate")]
        [ServiceFilter(typeof(PostFilter))]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)]
        [Consumes("application/x-www-form-urlencoded")]
        public IActionResult Locate([FromForm] string ID)
        {
            if (string.IsNullOrWhiteSpace(ID)) return BadRequest();

            if (!int.TryParse(ID, out var groupId)) return BadRequest();

            try
            {
                if (carPooling.ExistsGroup(groupId)) return NotFound();

                var journey = carPooling.Locate(groupId);

                if (journey == null) return NoContent();

                return Ok(journey.Car);
            }
            catch(Exception ex)
            {
                return BadRequest(ex);
            }
        }
    }
}<file_sep>/CarPooling.Domain/Business/Pooling.cs
using System.Collections.Generic;
using CarPooling.Domain.Models;

namespace CarPooling.Domain.Business
{
    public class Pooling
    {
        private Cars Cars { get; set; }
        private Groups Groups { get; set; }
        private Journeys Journeys { get; set; }

        public Pooling()
        {
            Cars = new Cars();
            Groups = new Groups();
            Journeys = new Journeys();
        }

        /// <summary>
        /// Clean the list of cars, groups and journeys
        /// Replaces a list of cars with a list passed by parameter
        /// </summary>
        /// <param name="cars"></param>
        public void UpdateCars(IEnumerable<Car> cars)
        {
            this.Cars.UpdateCars(cars);
            this.Groups.Clear();
            this.Journeys.Clear();
        }

        /// <summary>
        /// Alocate a group 
        /// </summary>
        /// <param name="group"></param>
        public void AlocateGroup(Group group)
        {
            this.Groups.Add(group);

            MatchJourneys();
        }

        /// <summary>
        /// Drop off a journey
        /// </summary>
        /// <param name="group"></param>
        public void DropOff(int groupId)
        {
            var car = Journeys.DropOff(groupId);

            Cars.Add(car);

            MatchJourneys();
        }

        public bool ExistsGroup(int groupId)
        {
            return Groups.ExistsGroup(groupId) ||
                   Journeys.ExistsJourney(groupId);
        }

        public Journey Locate(int groupId)
        {
            if (!Journeys.ExistsJourney(groupId)) return null;

            return Journeys.Locate(groupId);
        }

        private void MatchJourneys()
        {
            var seatsRequested = 0;
            var group = new Group();

            foreach (var groupId in Groups.GetGroupsId())
            {
                group = Groups.GetGroup(groupId);
                seatsRequested = group.People;

                while (seatsRequested <= 6)
                {
                    if (Cars.IsAvailable(seatsRequested))
                    {
                        var car = Cars.GetCarBySeats(seatsRequested);
                        CreateJourney(car, group);
                        break;
                    }

                    seatsRequested++;
                }
            }
        }

        private void CreateJourney(Car car, Group group)
        {
            Journeys.CreateJourney(car, group);

            Cars.Remove(car);
            Groups.Remove(group.Id);
        }
    }
}<file_sep>/CarPooling.Domain/Models/Car.cs
namespace CarPooling.Domain.Models
{
    public class Car
    {
        public int Id { get; set; }
        public int Seats { get; set; }
        public bool Valid => Validate();
        public bool Invalid => !Valid;

        private bool Validate() {
            if (Seats <= 0 || Seats >= 6) 
                return false;

            return true;
        }
    }
}<file_sep>/CarPooling.Domain/Models/Journey.cs
namespace CarPooling.Domain.Models
{
    public class Journey
    {
        public Car Car { get; set; }
        public Group Group { get; set; }

        public void DropOff(Group group)
        {
            Car.Seats += group.People;
        }
    }
}<file_sep>/Dockerfile
﻿FROM microsoft/dotnet:2.1-sdk  
WORKDIR /app  
EXPOSE 9091

COPY . .

RUN dotnet publish "./CarPooling.Api/CarPooling.Api.csproj" -c Release -o "../dist"

ENTRYPOINT ["dotnet", "dist/CarPooling.Api.dll"] <file_sep>/CarPooling.Api/Filters/PutFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace CarPooling.Api.Filters
{
    public class PutFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            var requestType = context.HttpContext.Request.Method;

            if (HttpMethods.IsPut(requestType)) 
                return;
                
            context.Result = new BadRequestObjectResult("Method is invalid.");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // Do something after the action executes.
        }
    }
}