├── .gitignore ├── Core ├── Core.csproj ├── Entities │ ├── Airport.cs │ ├── ApplicationUser.cs │ ├── BaseEntity.cs │ ├── City.cs │ ├── Country.cs │ ├── FlightInstance.cs │ ├── FlightRoute.cs │ ├── FlightTicket.cs │ └── RequestLog.cs ├── Interfaces │ ├── IAirportService.cs │ ├── IAsyncRepository.cs │ ├── IFlightRepository.cs │ ├── IFlightService.cs │ ├── IRepository.cs │ ├── IRequestLogRepository.cs │ ├── IRequestLogService.cs │ └── ISpecification.cs ├── Services │ ├── AirportService.cs │ ├── FlightService.cs │ └── RequestLogService.cs └── Specifications │ ├── AirportFilterByBeingADestinationForOrigin.cs │ ├── BaseSpecification.cs │ ├── FlightInstanceFilterByOriginAndDestination.cs │ ├── FlightInstanceFilterByOriginAndDestinationAndDate.cs │ └── FlightRouteFilterByOrigin.cs ├── Infrastructure ├── Data │ ├── AppDbContext.cs │ ├── DbSeeder.cs │ ├── EfRepository.cs │ ├── FlightRepository.cs │ └── RequestLogRepository.cs ├── Infrastructure.csproj ├── Migrations │ ├── 20180712235526_InitialMigration.Designer.cs │ ├── 20180712235526_InitialMigration.cs │ ├── 20180714183056_AirportFLightRouteNavigationProperties.Designer.cs │ ├── 20180714183056_AirportFLightRouteNavigationProperties.cs │ ├── 20180715194637_AddRequestLogEntity.Designer.cs │ ├── 20180715194637_AddRequestLogEntity.cs │ ├── 20180715201952_ModifyRequestLogEntityRemoveElapsedTime.Designer.cs │ ├── 20180715201952_ModifyRequestLogEntityRemoveElapsedTime.cs │ ├── 20180715202057_ModifyRequestLogEntityAddElapsedTime.Designer.cs │ ├── 20180715202057_ModifyRequestLogEntityAddElapsedTime.cs │ ├── 20180715204720_AddDateColumnToRequestLog.Designer.cs │ ├── 20180715204720_AddDateColumnToRequestLog.cs │ ├── 20180718213946_AddApplicationUser.Designer.cs │ ├── 20180718213946_AddApplicationUser.cs │ ├── 20180718230406_AddApplicationUserIdentity.Designer.cs │ ├── 20180718230406_AddApplicationUserIdentity.cs │ └── AppDbContextModelSnapshot.cs └── SqlScripts │ └── linkit_air_data.sql ├── LinkitAir.sln ├── LinkitAir ├── .gitignore ├── ActionFilterHelpers │ └── RequestActionFilter.cs ├── ClientApp │ ├── .angular-cli.json │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── e2e │ │ ├── app.e2e-spec.ts │ │ ├── app.po.ts │ │ └── tsconfig.e2e.json │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── protractor.conf.js │ ├── src │ │ ├── .gitattributes │ │ ├── app │ │ │ ├── admin │ │ │ │ ├── admin.component.css │ │ │ │ ├── admin.component.html │ │ │ │ └── admin.component.ts │ │ │ ├── airport │ │ │ │ ├── airport-list-component.css │ │ │ │ ├── airport-list-component.ts │ │ │ │ └── airport-list.component.html │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── home │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ │ ├── interfaces │ │ │ │ ├── airport.js │ │ │ │ ├── airport.js.map │ │ │ │ ├── airport.ts │ │ │ │ ├── flight.js │ │ │ │ ├── flight.js.map │ │ │ │ ├── flight.ts │ │ │ │ └── token-response.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.js │ │ │ │ ├── login.component.js.map │ │ │ │ └── login.component.ts │ │ │ ├── nav-menu │ │ │ │ ├── nav-menu.component.css │ │ │ │ ├── nav-menu.component.html │ │ │ │ └── nav-menu.component.ts │ │ │ └── services │ │ │ │ ├── airport-service.js │ │ │ │ ├── airport-service.js.map │ │ │ │ ├── airport-service.ts │ │ │ │ ├── auth-interceptor-service.js │ │ │ │ ├── auth-interceptor-service.js.map │ │ │ │ ├── auth-interceptor-service.ts │ │ │ │ ├── auth-service.js │ │ │ │ ├── auth-service.js.map │ │ │ │ ├── auth-service.ts │ │ │ │ ├── base-service.js │ │ │ │ ├── base-service.js.map │ │ │ │ ├── base-service.ts │ │ │ │ ├── flight-service.js │ │ │ │ ├── flight-service.js.map │ │ │ │ ├── flight-service.ts │ │ │ │ └── stats-service.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.js │ │ │ ├── environment.js.map │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ └── typings.d.ts │ ├── tsconfig.json │ └── tslint.json ├── Controllers │ ├── AdminController.cs │ ├── AirportController.cs │ ├── BaseController.cs │ ├── FlightController.cs │ └── TokenController.cs ├── CustomMiddleware │ ├── RequestResponseLoggingMiddleware.cs │ └── RequestResponseLoggingMiddlewareExtensions.cs ├── Helpers │ └── HttpRequestResponseHelper.cs ├── LinkitAir.csproj ├── Program.cs ├── Startup.cs ├── ViewModelHelpers │ └── FlightViewModelAdapterHelper.cs ├── ViewModels │ ├── AirportViewModel.cs │ ├── CityViewModel.cs │ ├── CountryViewModel.cs │ ├── FlightViewModel.cs │ ├── TokenRequestViewModel.cs │ └── TokenResponseViewModel.cs ├── api.xml ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ └── favicon.ico ├── README.md └── package-lock.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Core/Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Core/Entities/Airport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class Airport : BaseEntity 8 | { 9 | public string Code { get; set; } 10 | public string Name { get; set; } 11 | public string Description { get; set; } 12 | public int CityId { get; set; } 13 | public City City { get; set; } 14 | 15 | public ICollection Origins { get; set; } 16 | public ICollection Destinations { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core/Entities/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.AspNetCore.Identity; 5 | 6 | 7 | namespace Core.Entities 8 | { 9 | public class ApplicationUser : IdentityUser 10 | { 11 | public DateTime CreatedDate { get; set; } 12 | public DateTime LastModifiedDate { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/Entities/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public abstract class BaseEntity 8 | { 9 | public int Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Entities/City.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class City : BaseEntity 8 | { 9 | public string Name { get; set; } 10 | public int CountryId { get; set; } 11 | public Country Country { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Entities/Country.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class Country : BaseEntity 8 | { 9 | public string Name { get; set; } 10 | public string Code { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/Entities/FlightInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class FlightInstance : BaseEntity 8 | { 9 | public string Code { get; set; } 10 | public DateTimeOffset DepartureTime { get; set; } 11 | public DateTimeOffset ArrivalTime { get; set; } 12 | public int Price { get; set; } 13 | public int FlightRouteId { get; set; } 14 | public FlightRoute FlightRoute { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/Entities/FlightRoute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class FlightRoute : BaseEntity 8 | { 9 | public String Code { get; set; } 10 | public DateTimeOffset ValidFrom { get; set; } 11 | public DateTimeOffset ValidTo { get; set; } 12 | public int? OriginId { get; set; } 13 | public Airport Origin { get; set; } 14 | public int? DestinationId { get; set; } 15 | public Airport Destination { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Entities/FlightTicket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class FlightTicket : BaseEntity 8 | { 9 | public string Code { get; set; } 10 | public DateTimeOffset DatePurchased { get; set; } 11 | public int FlightInstanceId { get; set; } 12 | public FlightInstance FlightInstance { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/Entities/RequestLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Entities 6 | { 7 | public class RequestLog : BaseEntity 8 | { 9 | public string RequestMethod { get; set; } 10 | public string ResponseStatusCode { get; set; } 11 | public string UrlPath { get; set; } 12 | public long ElapsedTicks { get; set; } 13 | public DateTimeOffset CreatedAt { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Interfaces/IAirportService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | 4 | namespace Core.Interfaces 5 | { 6 | public interface IAirportService 7 | { 8 | IEnumerable GetAirports(); 9 | IEnumerable GetDestinationAirportsForOriginAirport(int originAirportId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Interfaces/IAsyncRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | using System.Threading.Tasks; 4 | 5 | namespace Core.Interfaces 6 | { 7 | public interface IAsyncRepository where T : BaseEntity 8 | { 9 | Task AddAsync(T entity); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Interfaces/IFlightRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System.Collections.Generic; 3 | 4 | namespace Core.Interfaces 5 | { 6 | public interface IFlightRepository : IRepository 7 | { 8 | IEnumerable GetAlternativeFlightsFromAndToSelectedCities(int originAirportId, int destinationAirportId, int originalResultFlightInstanceId, bool returnTicket); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/Interfaces/IFlightService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | 4 | namespace Core.Interfaces 5 | { 6 | public interface IFlightService 7 | { 8 | FlightInstance GetFlightForOriginAndDestinationAndDate(int originAirportId, int destinationAirportId, string flightDate); 9 | IEnumerable GetFlightsForOriginAndDestination(int originAirportId, int destinationAirportId, bool returnTicket); 10 | IEnumerable GetAlternativeFlightsFromAndToSelectedCities(int originAirportId, int destinationAirportId, int originalResultFlightInstanceId, bool returnTicket); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/Interfaces/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | 4 | namespace Core.Interfaces 5 | { 6 | public interface IRepository where T : BaseEntity 7 | { 8 | T GetById(int id); 9 | T GetSingleBySpec(ISpecification spec); 10 | IEnumerable GetAll(); 11 | IEnumerable Get(ISpecification spec); 12 | T Add(T entity); 13 | void Update(T entity); 14 | void Delete(T entity); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/Interfaces/IRequestLogRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System.Collections.Generic; 3 | 4 | namespace Core.Interfaces 5 | { 6 | public interface IRequestLogRepository : IRepository 7 | { 8 | int GetTotalNumberOfRequestsProcessed(); 9 | IEnumerable GetNumberOfRequestsByResponseCode(); 10 | int GetTotalNumberOfRequestsWithResponseCodeStartingWith(string startString); 11 | int GetTotalNumberOfRequestsWithResponseCode(string responseCode); 12 | double GetAverageResponseTime(); 13 | double GetMinResponseTime(); 14 | double GetMaxResponseTime(); 15 | IEnumerable GetStats(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Interfaces/IRequestLogService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Core.Interfaces 7 | { 8 | public interface IRequestLogService 9 | { 10 | Task CreateRequestLog(string RequestMethod, string ResponseStatusCode, string UrlPath, long ElapsedTicks); 11 | int GetTotalNumberOfRequestsProcessed(); 12 | IEnumerable GetNumberOfRequestsByResponseCode(); 13 | int GetTotalNumberOfRequestsWithResponseCodeStartingWith(string startString); 14 | int GetTotalNumberOfRequestsWithResponseCode(string responseCode); 15 | double GetAverageResponseTime(); 16 | double GetMinResponseTime(); 17 | double GetMaxResponseTime(); 18 | object GetStats(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Core/Interfaces/ISpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace Core.Interfaces 6 | { 7 | public interface ISpecification 8 | { 9 | Expression> Criteria { get; } 10 | List>> Includes { get; } 11 | List IncludeStrings { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Services/AirportService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | using Core.Interfaces; 4 | using Core.Specifications; 5 | using System.Linq; 6 | 7 | namespace Core.Services 8 | { 9 | public class AirportService : IAirportService 10 | { 11 | private readonly IRepository _airportRepository; 12 | private readonly IRepository _flightRouteRepository; 13 | 14 | public AirportService(IRepository airportRepository, IRepository flightRouteRepository) 15 | { 16 | _airportRepository = airportRepository; 17 | _flightRouteRepository = flightRouteRepository; 18 | } 19 | 20 | public IEnumerable GetAirports() 21 | { 22 | return _airportRepository.GetAll().ToList(); 23 | } 24 | 25 | public IEnumerable GetDestinationAirportsForOriginAirport(int originAirportId) 26 | { 27 | var airportSpec = new AirportFilterByBeingADestinationForOrigin(originAirportId); 28 | return _airportRepository.Get(airportSpec).ToList(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Core/Services/FlightService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Core.Entities; 3 | using Core.Interfaces; 4 | using Core.Specifications; 5 | using System.Linq; 6 | using System; 7 | 8 | namespace Core.Services 9 | { 10 | public class FlightService : IFlightService 11 | { 12 | private readonly IRepository _flightInstanceRepository; 13 | private readonly IRepository _flightRouteRepository; 14 | private readonly IFlightRepository _flightRepository; 15 | 16 | public FlightService(IRepository flightInstanceRepository, IRepository flightRouteRepository, 17 | IFlightRepository flightRepository) 18 | { 19 | _flightInstanceRepository = flightInstanceRepository; 20 | _flightRouteRepository = flightRouteRepository; 21 | _flightRepository = flightRepository; 22 | } 23 | 24 | public FlightInstance GetFlightForOriginAndDestinationAndDate(int originAirportId, int destinationAirportId, string flightDate) 25 | { 26 | var flightSpec = new FlightInstanceFilterByOriginAndDestinationAndDate(originAirportId, destinationAirportId, flightDate); 27 | return _flightInstanceRepository.GetSingleBySpec(flightSpec); 28 | } 29 | 30 | public IEnumerable GetFlightsForOriginAndDestination(int originAirportId, int destinationAirportId, bool returnTicket) 31 | { 32 | List flights = new List(); 33 | var originalFlightSpec = new FlightInstanceFilterByOriginAndDestination(originAirportId, destinationAirportId); 34 | var originalFlight = _flightInstanceRepository.Get(originalFlightSpec).SingleOrDefault(); 35 | if(originalFlight != null) 36 | { 37 | flights.Add(originalFlight); 38 | 39 | } 40 | if (returnTicket) { 41 | var returnFlightSpec = new FlightInstanceFilterByOriginAndDestination(destinationAirportId, originAirportId); 42 | var returnFlight = _flightInstanceRepository.Get(returnFlightSpec).SingleOrDefault(); 43 | if(returnFlight != null) 44 | { 45 | flights.Add(returnFlight); 46 | 47 | } 48 | } 49 | return flights; 50 | } 51 | 52 | public IEnumerable GetAlternativeFlightsFromAndToSelectedCities(int originAirportId, int destinationAirportId, int originalResultFlightInstanceId, bool returnTicket) 53 | { 54 | return _flightRepository.GetAlternativeFlightsFromAndToSelectedCities(originAirportId, destinationAirportId, originalResultFlightInstanceId, returnTicket); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Core/Services/RequestLogService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.Interfaces; 4 | using Core.Entities; 5 | using System.Threading.Tasks; 6 | using System.Linq; 7 | 8 | namespace Core.Services 9 | { 10 | public class RequestLogService : IRequestLogService 11 | { 12 | private readonly IRequestLogRepository _requestLogRepository; 13 | private readonly IAsyncRepository _asyncRequestLogRepository; 14 | 15 | 16 | public RequestLogService(IRequestLogRepository requestLogRepository, IAsyncRepository asyncRequestLogRepository) 17 | { 18 | _requestLogRepository = requestLogRepository; 19 | _asyncRequestLogRepository = asyncRequestLogRepository; 20 | } 21 | 22 | public async Task CreateRequestLog(string RequestMethod, string ResponseStatusCode, string UrlPath, long ElapsedTicks) 23 | { 24 | var requestLog = new RequestLog 25 | { 26 | RequestMethod = RequestMethod, 27 | ResponseStatusCode = ResponseStatusCode, 28 | UrlPath = UrlPath, 29 | ElapsedTicks = ElapsedTicks, 30 | CreatedAt = new DateTimeOffset(DateTime.Now) 31 | }; 32 | 33 | await _asyncRequestLogRepository.AddAsync(requestLog); 34 | } 35 | 36 | public int GetTotalNumberOfRequestsProcessed() 37 | { 38 | return _requestLogRepository.GetTotalNumberOfRequestsProcessed(); 39 | } 40 | 41 | public IEnumerable GetNumberOfRequestsByResponseCode() 42 | { 43 | return _requestLogRepository.GetNumberOfRequestsByResponseCode(); 44 | } 45 | 46 | public int GetTotalNumberOfRequestsWithResponseCodeStartingWith(string startString) 47 | { 48 | return _requestLogRepository.GetTotalNumberOfRequestsWithResponseCodeStartingWith(startString); 49 | } 50 | 51 | public int GetTotalNumberOfRequestsWithResponseCode(string responseCode) 52 | { 53 | return _requestLogRepository.GetTotalNumberOfRequestsWithResponseCode(responseCode); 54 | } 55 | 56 | public double GetAverageResponseTime() 57 | { 58 | return _requestLogRepository.GetAverageResponseTime(); 59 | } 60 | 61 | public double GetMinResponseTime() 62 | { 63 | return _requestLogRepository.GetMinResponseTime(); 64 | } 65 | 66 | public double GetMaxResponseTime() 67 | { 68 | return _requestLogRepository.GetMaxResponseTime(); 69 | } 70 | 71 | public object GetStats() 72 | { 73 | var stats = _requestLogRepository.GetStats().First(); 74 | var type = stats.GetType(); 75 | var count = (int)type.GetProperty("Count").GetValue(stats); 76 | var average = (double)type.GetProperty("Average").GetValue(stats); 77 | var maxValue = (long)type.GetProperty("Max").GetValue(stats); 78 | var minValue = (long)type.GetProperty("Min").GetValue(stats); 79 | 80 | return new 81 | { 82 | Count = count, 83 | Average = TimeSpan.FromTicks(Convert.ToInt64(average)).TotalSeconds, 84 | Max = TimeSpan.FromTicks(maxValue).TotalSeconds, 85 | Min = TimeSpan.FromTicks(minValue).TotalSeconds 86 | }; 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Core/Specifications/AirportFilterByBeingADestinationForOrigin.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System.Linq; 3 | 4 | namespace Core.Specifications 5 | { 6 | public class AirportFilterByBeingADestinationForOrigin : BaseSpecification 7 | { 8 | public AirportFilterByBeingADestinationForOrigin(int originAirportId) 9 | : base(a => a.Destinations.Any(f => f.DestinationId == a.Id && f.OriginId == originAirportId)) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Core/Specifications/BaseSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Collections.Generic; 4 | using Core.Interfaces; 5 | 6 | namespace Core.Specifications 7 | { 8 | public abstract class BaseSpecification : ISpecification 9 | { 10 | protected BaseSpecification(Expression> criteria) 11 | { 12 | Criteria = criteria; 13 | } 14 | public Expression> Criteria { get; } 15 | public List>> Includes { get; } = new List>>(); 16 | public List IncludeStrings { get; } = new List(); 17 | 18 | protected virtual void AddInclude(Expression> includeExpression) 19 | { 20 | Includes.Add(includeExpression); 21 | } 22 | protected virtual void AddInclude(string includeString) 23 | { 24 | IncludeStrings.Add(includeString); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Core/Specifications/FlightInstanceFilterByOriginAndDestination.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System; 3 | 4 | namespace Core.Specifications 5 | { 6 | public class FlightInstanceFilterByOriginAndDestination : BaseSpecification 7 | { 8 | public FlightInstanceFilterByOriginAndDestination(int originAirportId, int destinationAirportId) 9 | : base(f => f.FlightRoute.OriginId == originAirportId 10 | && f.FlightRoute.DestinationId == destinationAirportId 11 | ) 12 | { 13 | AddInclude(f => f.FlightRoute.Origin.City); 14 | AddInclude(f => f.FlightRoute.Destination.City); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Specifications/FlightInstanceFilterByOriginAndDestinationAndDate.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System; 3 | 4 | namespace Core.Specifications 5 | { 6 | public class FlightInstanceFilterByOriginAndDestinationAndDate : BaseSpecification 7 | { 8 | public FlightInstanceFilterByOriginAndDestinationAndDate(int originAirportId, int destinationAirportId, string flightDate) 9 | : base(f => f.FlightRoute.OriginId == originAirportId 10 | && f.FlightRoute.DestinationId == destinationAirportId 11 | && f.DepartureTime.ToString("dd-MM-yyyy") == flightDate 12 | ) 13 | { 14 | AddInclude(f => f.FlightRoute.Origin.City); 15 | AddInclude(f => f.FlightRoute.Destination.City); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Core/Specifications/FlightRouteFilterByOrigin.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | 3 | namespace Core.Specifications 4 | { 5 | public class FlightRouteFilterByOrigin : BaseSpecification 6 | { 7 | public FlightRouteFilterByOrigin(int originAirportId) 8 | : base(f => f.OriginId == originAirportId) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Infrastructure/Data/AppDbContext.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 5 | 6 | 7 | namespace Infrastructure.Data 8 | { 9 | public class AppDbContext : IdentityDbContext 10 | { 11 | public AppDbContext(DbContextOptions options) 12 | : base(options) 13 | { 14 | } 15 | 16 | public DbSet Airports { get; set; } 17 | public DbSet Cities { get; set; } 18 | public DbSet Countries { get; set; } 19 | public DbSet FlightInstances { get; set; } 20 | public DbSet FlightRoutes { get; set; } 21 | public DbSet FlightTickets { get; set; } 22 | public DbSet RequestLogs { get; set; } 23 | 24 | protected override void OnModelCreating(ModelBuilder builder) 25 | { 26 | base.OnModelCreating(builder); 27 | 28 | builder.Entity(ConfigureAirport); 29 | builder.Entity(ConfigureCity); 30 | builder.Entity(ConfigureCountry); 31 | builder.Entity(ConfigureFlightInstance); 32 | builder.Entity(ConfigureFlightRoute); 33 | builder.Entity(ConfigureFlightTicket); 34 | builder.Entity(ConfigureRequestLog); 35 | builder.Entity(ConfigureApplicationUser); 36 | } 37 | 38 | private void ConfigureAirport(EntityTypeBuilder builder) 39 | { 40 | builder.ToTable("Airport"); 41 | builder.HasKey(a => a.Id); 42 | builder.Property(a => a.Id).ValueGeneratedOnAdd(); 43 | builder.Property(a => a.Code).IsRequired().HasMaxLength(20); 44 | builder.Property(a => a.Name).IsRequired().HasMaxLength(50); 45 | builder.Property(a => a.Description).IsRequired().HasMaxLength(200); 46 | builder.HasOne(a => a.City).WithMany().HasForeignKey(a => a.CityId); 47 | builder.HasMany(a => a.Origins).WithOne(f => f.Origin); 48 | builder.HasMany(a => a.Destinations).WithOne(f => f.Destination); 49 | } 50 | 51 | private void ConfigureCity(EntityTypeBuilder builder) 52 | { 53 | builder.ToTable("City"); 54 | builder.HasKey(c => c.Id); 55 | builder.Property(c => c.Id).ValueGeneratedOnAdd().IsRequired(); 56 | builder.Property(c => c.Name).IsRequired().HasMaxLength(100); 57 | builder.HasOne(c => c.Country).WithMany().HasForeignKey(c => c.CountryId); 58 | } 59 | 60 | private void ConfigureCountry(EntityTypeBuilder builder) 61 | { 62 | builder.ToTable("Country"); 63 | builder.HasKey(c => c.Id); 64 | builder.Property(c => c.Id).ValueGeneratedOnAdd().IsRequired(); 65 | builder.Property(c => c.Code).IsRequired().HasMaxLength(20); 66 | builder.Property(c => c.Name).IsRequired().HasMaxLength(100); 67 | } 68 | 69 | private void ConfigureFlightInstance(EntityTypeBuilder builder) 70 | { 71 | builder.ToTable("FlightInstance"); 72 | builder.HasKey(f => f.Id); 73 | builder.Property(f => f.Id).ValueGeneratedOnAdd().IsRequired(); 74 | builder.Property(f => f.Code).IsRequired().HasMaxLength(20); 75 | builder.HasOne(f => f.FlightRoute).WithMany().HasForeignKey(f => f.FlightRouteId); 76 | } 77 | 78 | private void ConfigureFlightRoute(EntityTypeBuilder builder) 79 | { 80 | builder.ToTable("FlightRoute"); 81 | builder.HasKey(f => f.Id); 82 | builder.Property(f => f.Id).ValueGeneratedOnAdd().IsRequired(); 83 | builder.Property(f => f.Code).IsRequired().HasMaxLength(20); 84 | builder.HasOne(f => f.Origin).WithMany(a => a.Origins).HasForeignKey(f => f.OriginId).OnDelete(DeleteBehavior.Restrict); 85 | builder.HasOne(f => f.Destination).WithMany(a => a.Destinations).HasForeignKey(f => f.DestinationId).OnDelete(DeleteBehavior.Restrict); 86 | } 87 | 88 | private void ConfigureFlightTicket(EntityTypeBuilder builder) 89 | { 90 | builder.ToTable("FlightTicket"); 91 | builder.HasKey(f => f.Id); 92 | builder.Property(f => f.Id).ValueGeneratedOnAdd().IsRequired(); 93 | builder.Property(f => f.Code).IsRequired().HasMaxLength(20); 94 | builder.HasOne(f => f.FlightInstance).WithMany().HasForeignKey(f => f.FlightInstanceId); 95 | } 96 | 97 | private void ConfigureRequestLog(EntityTypeBuilder builder) 98 | { 99 | builder.ToTable("RequestLog"); 100 | builder.HasKey(r => r.Id); 101 | builder.Property(r => r.Id).ValueGeneratedOnAdd().IsRequired(); 102 | builder.Property(r => r.RequestMethod).IsRequired().HasMaxLength(20); 103 | builder.Property(r => r.ResponseStatusCode).IsRequired().HasMaxLength(20); 104 | builder.Property(r => r.UrlPath).IsRequired().HasMaxLength(200); 105 | } 106 | 107 | private void ConfigureApplicationUser(EntityTypeBuilder builder) 108 | { 109 | builder.ToTable("Users"); 110 | builder.HasKey(r => r.Id); 111 | builder.Property(r => r.Id).ValueGeneratedOnAdd().IsRequired(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Infrastructure/Data/DbSeeder.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using Infrastructure.Data; 3 | using Microsoft.AspNetCore.Identity; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Infrastructure.Data 9 | { 10 | public static class DbSeeder 11 | { 12 | 13 | public static void Seed(AppDbContext dbContext, RoleManager roleManager, UserManager userManager) 14 | { 15 | if (!dbContext.Users.Any()) 16 | { 17 | CreateUsers(dbContext, roleManager, userManager).GetAwaiter().GetResult(); 18 | } 19 | } 20 | 21 | private static async Task CreateUsers(AppDbContext dbContext, RoleManager roleManager, UserManager userManager) 22 | { 23 | DateTime createdDate = new DateTime(2018, 07, 01, 12, 30, 00); 24 | DateTime lastModifiedDate = DateTime.Now; 25 | 26 | string role_Administrator = "Administrator"; 27 | 28 | string role_RegisteredUser = "RegisteredUser"; 29 | if (!await roleManager.RoleExistsAsync(role_Administrator)) 30 | { 31 | await roleManager.CreateAsync(new 32 | IdentityRole(role_Administrator)); 33 | } 34 | if (!await roleManager.RoleExistsAsync(role_RegisteredUser)) 35 | { 36 | await roleManager.CreateAsync(new 37 | IdentityRole(role_RegisteredUser)); 38 | } 39 | 40 | var user_Admin = new ApplicationUser() 41 | { 42 | SecurityStamp = Guid.NewGuid().ToString(), 43 | UserName = "Admin", 44 | Email = "robert.gliguroski@gmail.com", 45 | CreatedDate = createdDate, 46 | LastModifiedDate = lastModifiedDate 47 | }; 48 | 49 | if (await userManager.FindByNameAsync(user_Admin.UserName) == null) 50 | { 51 | await userManager.CreateAsync(user_Admin, "Pass4Admin"); 52 | await userManager.AddToRoleAsync(user_Admin, 53 | role_RegisteredUser); 54 | await userManager.AddToRoleAsync(user_Admin, 55 | role_Administrator); 56 | // Remove Lockout and E-Mail confirmation. 57 | user_Admin.EmailConfirmed = true; 58 | user_Admin.LockoutEnabled = false; 59 | } 60 | 61 | await dbContext.SaveChangesAsync(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Infrastructure/Data/EfRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using Core.Interfaces; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Infrastructure.Data 9 | { 10 | public class EfRepository : IRepository, IAsyncRepository where T : BaseEntity 11 | { 12 | protected readonly AppDbContext _dbContext; 13 | 14 | public EfRepository(AppDbContext dbContext) 15 | { 16 | _dbContext = dbContext; 17 | } 18 | 19 | public T GetById(int id) 20 | { 21 | return _dbContext.Set().Find(id); 22 | } 23 | 24 | public T GetSingleBySpec(ISpecification spec) 25 | { 26 | return Get(spec).FirstOrDefault(); 27 | } 28 | 29 | public IEnumerable GetAll() 30 | { 31 | return _dbContext.Set().AsEnumerable(); 32 | } 33 | 34 | public IEnumerable Get(ISpecification spec) 35 | { 36 | // fetch a Queryable that includes all expression-based includes 37 | var queryableResultWithIncludes = spec.Includes 38 | .Aggregate(_dbContext.Set().AsQueryable(), 39 | (current, include) => current.Include(include)); 40 | 41 | // modify the IQueryable to include any string-based include statements 42 | var secondaryResult = spec.IncludeStrings 43 | .Aggregate(queryableResultWithIncludes, 44 | (current, include) => current.Include(include)); 45 | 46 | // return the result of the query using the specification's criteria expression 47 | return secondaryResult 48 | .Where(spec.Criteria) 49 | .AsEnumerable(); 50 | } 51 | 52 | public T Add(T entity) 53 | { 54 | _dbContext.Set().Add(entity); 55 | _dbContext.SaveChanges(); 56 | 57 | return entity; 58 | } 59 | 60 | public async Task AddAsync(T entity) 61 | { 62 | _dbContext.Set().Add(entity); 63 | await _dbContext.SaveChangesAsync(); 64 | 65 | return entity; 66 | } 67 | 68 | public void Update(T entity) 69 | { 70 | _dbContext.Entry(entity).State = EntityState.Modified; 71 | _dbContext.SaveChanges(); 72 | } 73 | 74 | public void Delete(T entity) 75 | { 76 | _dbContext.Set().Remove(entity); 77 | _dbContext.SaveChanges(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Infrastructure/Data/FlightRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Core.Entities; 3 | using Core.Interfaces; 4 | using System.Collections.Generic; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace Infrastructure.Data 8 | { 9 | public class FlightRepository : EfRepository, IFlightRepository 10 | { 11 | public FlightRepository(AppDbContext dbContext) : base(dbContext) 12 | { 13 | } 14 | 15 | public IEnumerable GetAlternativeFlightsFromAndToSelectedCities(int originAirportId, int destinationAirportId, int originalResultFlightInstanceId, bool returnTicket) 16 | { 17 | var originCityId = _dbContext.Airports.Where(a => a.Id == originAirportId).Select(a => a.CityId).First(); 18 | var destinationCityId = _dbContext.Airports.Where(a => a.Id == destinationAirportId).Select(a => a.CityId).First(); 19 | 20 | var flights = 21 | (from flightInstance in _dbContext.FlightInstances 22 | join flightRoute in _dbContext.FlightRoutes on flightInstance.FlightRouteId equals flightRoute.Id 23 | where flightInstance.FlightRoute.Origin.CityId == originCityId 24 | && flightInstance.FlightRoute.Destination.CityId == destinationCityId 25 | && flightInstance.Id != originalResultFlightInstanceId 26 | select flightInstance) 27 | .Include(f => f.FlightRoute) 28 | .ThenInclude(fr => fr.Origin) 29 | .ThenInclude(o => o.City) 30 | .Include(f => f.FlightRoute) 31 | .ThenInclude(fr => fr.Destination) 32 | .ThenInclude(d => d.City); 33 | 34 | return flights.ToList(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Infrastructure/Data/RequestLogRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using Core.Interfaces; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using System; 6 | 7 | namespace Infrastructure.Data 8 | { 9 | public class RequestLogRepository : EfRepository, IRequestLogRepository 10 | { 11 | public RequestLogRepository(AppDbContext dbContext) : base(dbContext) 12 | { 13 | } 14 | 15 | public int GetTotalNumberOfRequestsProcessed() 16 | { 17 | return _dbContext.RequestLogs.Count(); 18 | } 19 | 20 | public IEnumerable GetNumberOfRequestsByResponseCode() 21 | { 22 | var logs = ( 23 | from requestLog in _dbContext.RequestLogs 24 | group requestLog by requestLog.ResponseStatusCode into g 25 | select new 26 | { 27 | ResponseStatusCode = g.Key, 28 | NumberOfRequestPerResponseCode = g.Count() 29 | }); 30 | return logs.ToList(); 31 | } 32 | 33 | public int GetTotalNumberOfRequestsWithResponseCodeStartingWith(string startString) 34 | { 35 | return _dbContext.RequestLogs.Where(r => r.ResponseStatusCode.StartsWith(startString)).Count(); 36 | } 37 | 38 | public int GetTotalNumberOfRequestsWithResponseCode(string responseCode) 39 | { 40 | return _dbContext.RequestLogs.Where(r => r.ResponseStatusCode == responseCode).Count(); 41 | } 42 | 43 | public double GetAverageResponseTime() 44 | { 45 | return _dbContext.RequestLogs.Average(r => r.ElapsedTicks); 46 | } 47 | 48 | public double GetMinResponseTime() 49 | { 50 | return _dbContext.RequestLogs.Min(r => r.ElapsedTicks); 51 | } 52 | 53 | public double GetMaxResponseTime() 54 | { 55 | return _dbContext.RequestLogs.Max(r => r.ElapsedTicks); 56 | } 57 | 58 | public IEnumerable GetStats() 59 | { 60 | var stats = _dbContext.RequestLogs.GroupBy(i => 1) 61 | .Select(g => new { 62 | Count = g.Count(), 63 | Average = g.Average(r => r.ElapsedTicks), 64 | Min = g.Min(r => r.ElapsedTicks), 65 | Max = g.Max(r => r.ElapsedTicks) 66 | }); 67 | return stats.ToList(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Infrastructure/Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180712235526_InitialMigration.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace Infrastructure.Migrations 11 | { 12 | [DbContext(typeof(AppDbContext))] 13 | [Migration("20180712235526_InitialMigration")] 14 | partial class InitialMigration 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("Core.Entities.Airport", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("CityId"); 31 | 32 | b.Property("Code") 33 | .IsRequired() 34 | .HasMaxLength(20); 35 | 36 | b.Property("Description") 37 | .IsRequired() 38 | .HasMaxLength(200); 39 | 40 | b.Property("Name") 41 | .IsRequired() 42 | .HasMaxLength(50); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.HasIndex("CityId"); 47 | 48 | b.ToTable("Airport"); 49 | }); 50 | 51 | modelBuilder.Entity("Core.Entities.City", b => 52 | { 53 | b.Property("Id") 54 | .ValueGeneratedOnAdd() 55 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 56 | 57 | b.Property("CountryId"); 58 | 59 | b.Property("Name") 60 | .IsRequired() 61 | .HasMaxLength(100); 62 | 63 | b.HasKey("Id"); 64 | 65 | b.HasIndex("CountryId"); 66 | 67 | b.ToTable("City"); 68 | }); 69 | 70 | modelBuilder.Entity("Core.Entities.Country", b => 71 | { 72 | b.Property("Id") 73 | .ValueGeneratedOnAdd() 74 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 75 | 76 | b.Property("Code") 77 | .IsRequired() 78 | .HasMaxLength(20); 79 | 80 | b.Property("Name") 81 | .IsRequired() 82 | .HasMaxLength(100); 83 | 84 | b.HasKey("Id"); 85 | 86 | b.ToTable("Country"); 87 | }); 88 | 89 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 90 | { 91 | b.Property("Id") 92 | .ValueGeneratedOnAdd() 93 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 94 | 95 | b.Property("ArrivalTime"); 96 | 97 | b.Property("Code") 98 | .IsRequired() 99 | .HasMaxLength(20); 100 | 101 | b.Property("DepartureTime"); 102 | 103 | b.Property("FlightRouteId"); 104 | 105 | b.Property("Price"); 106 | 107 | b.HasKey("Id"); 108 | 109 | b.HasIndex("FlightRouteId"); 110 | 111 | b.ToTable("FlightInstance"); 112 | }); 113 | 114 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 115 | { 116 | b.Property("Id") 117 | .ValueGeneratedOnAdd() 118 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 119 | 120 | b.Property("Code") 121 | .IsRequired() 122 | .HasMaxLength(20); 123 | 124 | b.Property("DestinationId"); 125 | 126 | b.Property("OriginId"); 127 | 128 | b.Property("ValidFrom"); 129 | 130 | b.Property("ValidTo"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("DestinationId"); 135 | 136 | b.HasIndex("OriginId"); 137 | 138 | b.ToTable("FlightRoute"); 139 | }); 140 | 141 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 142 | { 143 | b.Property("Id") 144 | .ValueGeneratedOnAdd() 145 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 146 | 147 | b.Property("Code") 148 | .IsRequired() 149 | .HasMaxLength(20); 150 | 151 | b.Property("DatePurchased"); 152 | 153 | b.Property("FlightInstanceId"); 154 | 155 | b.HasKey("Id"); 156 | 157 | b.HasIndex("FlightInstanceId"); 158 | 159 | b.ToTable("FlightTicket"); 160 | }); 161 | 162 | modelBuilder.Entity("Core.Entities.Airport", b => 163 | { 164 | b.HasOne("Core.Entities.City", "City") 165 | .WithMany() 166 | .HasForeignKey("CityId") 167 | .OnDelete(DeleteBehavior.Cascade); 168 | }); 169 | 170 | modelBuilder.Entity("Core.Entities.City", b => 171 | { 172 | b.HasOne("Core.Entities.Country", "Country") 173 | .WithMany() 174 | .HasForeignKey("CountryId") 175 | .OnDelete(DeleteBehavior.Cascade); 176 | }); 177 | 178 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 179 | { 180 | b.HasOne("Core.Entities.FlightRoute", "FlightRoute") 181 | .WithMany() 182 | .HasForeignKey("FlightRouteId") 183 | .OnDelete(DeleteBehavior.Cascade); 184 | }); 185 | 186 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 187 | { 188 | b.HasOne("Core.Entities.Airport", "Destination") 189 | .WithMany() 190 | .HasForeignKey("DestinationId") 191 | .OnDelete(DeleteBehavior.Cascade); 192 | 193 | b.HasOne("Core.Entities.Airport", "Origin") 194 | .WithMany() 195 | .HasForeignKey("OriginId") 196 | .OnDelete(DeleteBehavior.Cascade); 197 | }); 198 | 199 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 200 | { 201 | b.HasOne("Core.Entities.FlightInstance", "FlightInstance") 202 | .WithMany() 203 | .HasForeignKey("FlightInstanceId") 204 | .OnDelete(DeleteBehavior.Cascade); 205 | }); 206 | #pragma warning restore 612, 618 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180712235526_InitialMigration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace Infrastructure.Migrations 6 | { 7 | public partial class InitialMigration : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Country", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false) 16 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 17 | Name = table.Column(maxLength: 100, nullable: false), 18 | Code = table.Column(maxLength: 20, nullable: false) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_Country", x => x.Id); 23 | }); 24 | 25 | migrationBuilder.CreateTable( 26 | name: "City", 27 | columns: table => new 28 | { 29 | Id = table.Column(nullable: false) 30 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 31 | Name = table.Column(maxLength: 100, nullable: false), 32 | CountryId = table.Column(nullable: false) 33 | }, 34 | constraints: table => 35 | { 36 | table.PrimaryKey("PK_City", x => x.Id); 37 | table.ForeignKey( 38 | name: "FK_City_Country_CountryId", 39 | column: x => x.CountryId, 40 | principalTable: "Country", 41 | principalColumn: "Id", 42 | onDelete: ReferentialAction.Cascade); 43 | }); 44 | 45 | migrationBuilder.CreateTable( 46 | name: "Airport", 47 | columns: table => new 48 | { 49 | Id = table.Column(nullable: false) 50 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 51 | Code = table.Column(maxLength: 20, nullable: false), 52 | Name = table.Column(maxLength: 50, nullable: false), 53 | Description = table.Column(maxLength: 200, nullable: false), 54 | CityId = table.Column(nullable: false) 55 | }, 56 | constraints: table => 57 | { 58 | table.PrimaryKey("PK_Airport", x => x.Id); 59 | table.ForeignKey( 60 | name: "FK_Airport_City_CityId", 61 | column: x => x.CityId, 62 | principalTable: "City", 63 | principalColumn: "Id", 64 | onDelete: ReferentialAction.Cascade); 65 | }); 66 | 67 | migrationBuilder.CreateTable( 68 | name: "FlightRoute", 69 | columns: table => new 70 | { 71 | Id = table.Column(nullable: false) 72 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 73 | Code = table.Column(maxLength: 20, nullable: false), 74 | ValidFrom = table.Column(nullable: false), 75 | ValidTo = table.Column(nullable: false), 76 | OriginId = table.Column(nullable: true), 77 | DestinationId = table.Column(nullable: true) 78 | }, 79 | constraints: table => 80 | { 81 | table.PrimaryKey("PK_FlightRoute", x => x.Id); 82 | table.ForeignKey( 83 | name: "FK_FlightRoute_Airport_DestinationId", 84 | column: x => x.DestinationId, 85 | principalTable: "Airport", 86 | principalColumn: "Id", 87 | onDelete: ReferentialAction.NoAction); 88 | table.ForeignKey( 89 | name: "FK_FlightRoute_Airport_OriginId", 90 | column: x => x.OriginId, 91 | principalTable: "Airport", 92 | principalColumn: "Id", 93 | onDelete: ReferentialAction.NoAction); 94 | }); 95 | 96 | migrationBuilder.CreateTable( 97 | name: "FlightInstance", 98 | columns: table => new 99 | { 100 | Id = table.Column(nullable: false) 101 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 102 | Code = table.Column(maxLength: 20, nullable: false), 103 | DepartureTime = table.Column(nullable: false), 104 | ArrivalTime = table.Column(nullable: false), 105 | Price = table.Column(nullable: false), 106 | FlightRouteId = table.Column(nullable: false) 107 | }, 108 | constraints: table => 109 | { 110 | table.PrimaryKey("PK_FlightInstance", x => x.Id); 111 | table.ForeignKey( 112 | name: "FK_FlightInstance_FlightRoute_FlightRouteId", 113 | column: x => x.FlightRouteId, 114 | principalTable: "FlightRoute", 115 | principalColumn: "Id", 116 | onDelete: ReferentialAction.Cascade); 117 | }); 118 | 119 | migrationBuilder.CreateTable( 120 | name: "FlightTicket", 121 | columns: table => new 122 | { 123 | Id = table.Column(nullable: false) 124 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 125 | Code = table.Column(maxLength: 20, nullable: false), 126 | DatePurchased = table.Column(nullable: false), 127 | FlightInstanceId = table.Column(nullable: false) 128 | }, 129 | constraints: table => 130 | { 131 | table.PrimaryKey("PK_FlightTicket", x => x.Id); 132 | table.ForeignKey( 133 | name: "FK_FlightTicket_FlightInstance_FlightInstanceId", 134 | column: x => x.FlightInstanceId, 135 | principalTable: "FlightInstance", 136 | principalColumn: "Id", 137 | onDelete: ReferentialAction.Cascade); 138 | }); 139 | 140 | migrationBuilder.CreateIndex( 141 | name: "IX_Airport_CityId", 142 | table: "Airport", 143 | column: "CityId"); 144 | 145 | migrationBuilder.CreateIndex( 146 | name: "IX_City_CountryId", 147 | table: "City", 148 | column: "CountryId"); 149 | 150 | migrationBuilder.CreateIndex( 151 | name: "IX_FlightInstance_FlightRouteId", 152 | table: "FlightInstance", 153 | column: "FlightRouteId"); 154 | 155 | migrationBuilder.CreateIndex( 156 | name: "IX_FlightRoute_DestinationId", 157 | table: "FlightRoute", 158 | column: "DestinationId"); 159 | 160 | migrationBuilder.CreateIndex( 161 | name: "IX_FlightRoute_OriginId", 162 | table: "FlightRoute", 163 | column: "OriginId"); 164 | 165 | migrationBuilder.CreateIndex( 166 | name: "IX_FlightTicket_FlightInstanceId", 167 | table: "FlightTicket", 168 | column: "FlightInstanceId"); 169 | } 170 | 171 | protected override void Down(MigrationBuilder migrationBuilder) 172 | { 173 | migrationBuilder.DropTable( 174 | name: "FlightTicket"); 175 | 176 | migrationBuilder.DropTable( 177 | name: "FlightInstance"); 178 | 179 | migrationBuilder.DropTable( 180 | name: "FlightRoute"); 181 | 182 | migrationBuilder.DropTable( 183 | name: "Airport"); 184 | 185 | migrationBuilder.DropTable( 186 | name: "City"); 187 | 188 | migrationBuilder.DropTable( 189 | name: "Country"); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180714183056_AirportFLightRouteNavigationProperties.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace Infrastructure.Migrations 11 | { 12 | [DbContext(typeof(AppDbContext))] 13 | [Migration("20180714183056_AirportFLightRouteNavigationProperties")] 14 | partial class AirportFLightRouteNavigationProperties 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("Core.Entities.Airport", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("CityId"); 31 | 32 | b.Property("Code") 33 | .IsRequired() 34 | .HasMaxLength(20); 35 | 36 | b.Property("Description") 37 | .IsRequired() 38 | .HasMaxLength(200); 39 | 40 | b.Property("Name") 41 | .IsRequired() 42 | .HasMaxLength(50); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.HasIndex("CityId"); 47 | 48 | b.ToTable("Airport"); 49 | }); 50 | 51 | modelBuilder.Entity("Core.Entities.City", b => 52 | { 53 | b.Property("Id") 54 | .ValueGeneratedOnAdd() 55 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 56 | 57 | b.Property("CountryId"); 58 | 59 | b.Property("Name") 60 | .IsRequired() 61 | .HasMaxLength(100); 62 | 63 | b.HasKey("Id"); 64 | 65 | b.HasIndex("CountryId"); 66 | 67 | b.ToTable("City"); 68 | }); 69 | 70 | modelBuilder.Entity("Core.Entities.Country", b => 71 | { 72 | b.Property("Id") 73 | .ValueGeneratedOnAdd() 74 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 75 | 76 | b.Property("Code") 77 | .IsRequired() 78 | .HasMaxLength(20); 79 | 80 | b.Property("Name") 81 | .IsRequired() 82 | .HasMaxLength(100); 83 | 84 | b.HasKey("Id"); 85 | 86 | b.ToTable("Country"); 87 | }); 88 | 89 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 90 | { 91 | b.Property("Id") 92 | .ValueGeneratedOnAdd() 93 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 94 | 95 | b.Property("ArrivalTime"); 96 | 97 | b.Property("Code") 98 | .IsRequired() 99 | .HasMaxLength(20); 100 | 101 | b.Property("DepartureTime"); 102 | 103 | b.Property("FlightRouteId"); 104 | 105 | b.Property("Price"); 106 | 107 | b.HasKey("Id"); 108 | 109 | b.HasIndex("FlightRouteId"); 110 | 111 | b.ToTable("FlightInstance"); 112 | }); 113 | 114 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 115 | { 116 | b.Property("Id") 117 | .ValueGeneratedOnAdd() 118 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 119 | 120 | b.Property("Code") 121 | .IsRequired() 122 | .HasMaxLength(20); 123 | 124 | b.Property("DestinationId"); 125 | 126 | b.Property("OriginId"); 127 | 128 | b.Property("ValidFrom"); 129 | 130 | b.Property("ValidTo"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("DestinationId"); 135 | 136 | b.HasIndex("OriginId"); 137 | 138 | b.ToTable("FlightRoute"); 139 | }); 140 | 141 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 142 | { 143 | b.Property("Id") 144 | .ValueGeneratedOnAdd() 145 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 146 | 147 | b.Property("Code") 148 | .IsRequired() 149 | .HasMaxLength(20); 150 | 151 | b.Property("DatePurchased"); 152 | 153 | b.Property("FlightInstanceId"); 154 | 155 | b.HasKey("Id"); 156 | 157 | b.HasIndex("FlightInstanceId"); 158 | 159 | b.ToTable("FlightTicket"); 160 | }); 161 | 162 | modelBuilder.Entity("Core.Entities.Airport", b => 163 | { 164 | b.HasOne("Core.Entities.City", "City") 165 | .WithMany() 166 | .HasForeignKey("CityId") 167 | .OnDelete(DeleteBehavior.Cascade); 168 | }); 169 | 170 | modelBuilder.Entity("Core.Entities.City", b => 171 | { 172 | b.HasOne("Core.Entities.Country", "Country") 173 | .WithMany() 174 | .HasForeignKey("CountryId") 175 | .OnDelete(DeleteBehavior.Cascade); 176 | }); 177 | 178 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 179 | { 180 | b.HasOne("Core.Entities.FlightRoute", "FlightRoute") 181 | .WithMany() 182 | .HasForeignKey("FlightRouteId") 183 | .OnDelete(DeleteBehavior.Cascade); 184 | }); 185 | 186 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 187 | { 188 | b.HasOne("Core.Entities.Airport", "Destination") 189 | .WithMany("Destinations") 190 | .HasForeignKey("DestinationId") 191 | .OnDelete(DeleteBehavior.Restrict); 192 | 193 | b.HasOne("Core.Entities.Airport", "Origin") 194 | .WithMany("Origins") 195 | .HasForeignKey("OriginId") 196 | .OnDelete(DeleteBehavior.Restrict); 197 | }); 198 | 199 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 200 | { 201 | b.HasOne("Core.Entities.FlightInstance", "FlightInstance") 202 | .WithMany() 203 | .HasForeignKey("FlightInstanceId") 204 | .OnDelete(DeleteBehavior.Cascade); 205 | }); 206 | #pragma warning restore 612, 618 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180714183056_AirportFLightRouteNavigationProperties.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace Infrastructure.Migrations 4 | { 5 | public partial class AirportFLightRouteNavigationProperties : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.DropForeignKey( 10 | name: "FK_FlightRoute_Airport_DestinationId", 11 | table: "FlightRoute"); 12 | 13 | migrationBuilder.DropForeignKey( 14 | name: "FK_FlightRoute_Airport_OriginId", 15 | table: "FlightRoute"); 16 | 17 | migrationBuilder.AlterColumn( 18 | name: "OriginId", 19 | table: "FlightRoute", 20 | nullable: true, 21 | oldClrType: typeof(int)); 22 | 23 | migrationBuilder.AlterColumn( 24 | name: "DestinationId", 25 | table: "FlightRoute", 26 | nullable: true, 27 | oldClrType: typeof(int)); 28 | 29 | migrationBuilder.AddForeignKey( 30 | name: "FK_FlightRoute_Airport_DestinationId", 31 | table: "FlightRoute", 32 | column: "DestinationId", 33 | principalTable: "Airport", 34 | principalColumn: "Id", 35 | onDelete: ReferentialAction.Restrict); 36 | 37 | migrationBuilder.AddForeignKey( 38 | name: "FK_FlightRoute_Airport_OriginId", 39 | table: "FlightRoute", 40 | column: "OriginId", 41 | principalTable: "Airport", 42 | principalColumn: "Id", 43 | onDelete: ReferentialAction.Restrict); 44 | } 45 | 46 | protected override void Down(MigrationBuilder migrationBuilder) 47 | { 48 | migrationBuilder.DropForeignKey( 49 | name: "FK_FlightRoute_Airport_DestinationId", 50 | table: "FlightRoute"); 51 | 52 | migrationBuilder.DropForeignKey( 53 | name: "FK_FlightRoute_Airport_OriginId", 54 | table: "FlightRoute"); 55 | 56 | migrationBuilder.AlterColumn( 57 | name: "OriginId", 58 | table: "FlightRoute", 59 | nullable: false, 60 | oldClrType: typeof(int), 61 | oldNullable: true); 62 | 63 | migrationBuilder.AlterColumn( 64 | name: "DestinationId", 65 | table: "FlightRoute", 66 | nullable: false, 67 | oldClrType: typeof(int), 68 | oldNullable: true); 69 | 70 | migrationBuilder.AddForeignKey( 71 | name: "FK_FlightRoute_Airport_DestinationId", 72 | table: "FlightRoute", 73 | column: "DestinationId", 74 | principalTable: "Airport", 75 | principalColumn: "Id", 76 | onDelete: ReferentialAction.Cascade); 77 | 78 | migrationBuilder.AddForeignKey( 79 | name: "FK_FlightRoute_Airport_OriginId", 80 | table: "FlightRoute", 81 | column: "OriginId", 82 | principalTable: "Airport", 83 | principalColumn: "Id", 84 | onDelete: ReferentialAction.Cascade); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180715194637_AddRequestLogEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace Infrastructure.Migrations 6 | { 7 | public partial class AddRequestLogEntity : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "RequestLog", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false) 16 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 17 | RequestMethod = table.Column(maxLength: 20, nullable: false), 18 | ResponseStatusCode = table.Column(maxLength: 20, nullable: false), 19 | UrlPath = table.Column(maxLength: 200, nullable: false), 20 | ElapsedTicks = table.Column(nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_RequestLog", x => x.Id); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "RequestLog"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180715201952_ModifyRequestLogEntityRemoveElapsedTime.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace Infrastructure.Migrations 11 | { 12 | [DbContext(typeof(AppDbContext))] 13 | [Migration("20180715201952_ModifyRequestLogEntityRemoveElapsedTime")] 14 | partial class ModifyRequestLogEntityRemoveElapsedTime 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("Core.Entities.Airport", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 29 | 30 | b.Property("CityId"); 31 | 32 | b.Property("Code") 33 | .IsRequired() 34 | .HasMaxLength(20); 35 | 36 | b.Property("Description") 37 | .IsRequired() 38 | .HasMaxLength(200); 39 | 40 | b.Property("Name") 41 | .IsRequired() 42 | .HasMaxLength(50); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.HasIndex("CityId"); 47 | 48 | b.ToTable("Airport"); 49 | }); 50 | 51 | modelBuilder.Entity("Core.Entities.City", b => 52 | { 53 | b.Property("Id") 54 | .ValueGeneratedOnAdd() 55 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 56 | 57 | b.Property("CountryId"); 58 | 59 | b.Property("Name") 60 | .IsRequired() 61 | .HasMaxLength(100); 62 | 63 | b.HasKey("Id"); 64 | 65 | b.HasIndex("CountryId"); 66 | 67 | b.ToTable("City"); 68 | }); 69 | 70 | modelBuilder.Entity("Core.Entities.Country", b => 71 | { 72 | b.Property("Id") 73 | .ValueGeneratedOnAdd() 74 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 75 | 76 | b.Property("Code") 77 | .IsRequired() 78 | .HasMaxLength(20); 79 | 80 | b.Property("Name") 81 | .IsRequired() 82 | .HasMaxLength(100); 83 | 84 | b.HasKey("Id"); 85 | 86 | b.ToTable("Country"); 87 | }); 88 | 89 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 90 | { 91 | b.Property("Id") 92 | .ValueGeneratedOnAdd() 93 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 94 | 95 | b.Property("ArrivalTime"); 96 | 97 | b.Property("Code") 98 | .IsRequired() 99 | .HasMaxLength(20); 100 | 101 | b.Property("DepartureTime"); 102 | 103 | b.Property("FlightRouteId"); 104 | 105 | b.Property("Price"); 106 | 107 | b.HasKey("Id"); 108 | 109 | b.HasIndex("FlightRouteId"); 110 | 111 | b.ToTable("FlightInstance"); 112 | }); 113 | 114 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 115 | { 116 | b.Property("Id") 117 | .ValueGeneratedOnAdd() 118 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 119 | 120 | b.Property("Code") 121 | .IsRequired() 122 | .HasMaxLength(20); 123 | 124 | b.Property("DestinationId"); 125 | 126 | b.Property("OriginId"); 127 | 128 | b.Property("ValidFrom"); 129 | 130 | b.Property("ValidTo"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("DestinationId"); 135 | 136 | b.HasIndex("OriginId"); 137 | 138 | b.ToTable("FlightRoute"); 139 | }); 140 | 141 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 142 | { 143 | b.Property("Id") 144 | .ValueGeneratedOnAdd() 145 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 146 | 147 | b.Property("Code") 148 | .IsRequired() 149 | .HasMaxLength(20); 150 | 151 | b.Property("DatePurchased"); 152 | 153 | b.Property("FlightInstanceId"); 154 | 155 | b.HasKey("Id"); 156 | 157 | b.HasIndex("FlightInstanceId"); 158 | 159 | b.ToTable("FlightTicket"); 160 | }); 161 | 162 | modelBuilder.Entity("Core.Entities.RequestLog", b => 163 | { 164 | b.Property("Id") 165 | .ValueGeneratedOnAdd() 166 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 167 | 168 | b.Property("RequestMethod") 169 | .IsRequired() 170 | .HasMaxLength(20); 171 | 172 | b.Property("ResponseStatusCode") 173 | .IsRequired() 174 | .HasMaxLength(20); 175 | 176 | b.Property("UrlPath") 177 | .IsRequired() 178 | .HasMaxLength(200); 179 | 180 | b.HasKey("Id"); 181 | 182 | b.ToTable("RequestLog"); 183 | }); 184 | 185 | modelBuilder.Entity("Core.Entities.Airport", b => 186 | { 187 | b.HasOne("Core.Entities.City", "City") 188 | .WithMany() 189 | .HasForeignKey("CityId") 190 | .OnDelete(DeleteBehavior.Cascade); 191 | }); 192 | 193 | modelBuilder.Entity("Core.Entities.City", b => 194 | { 195 | b.HasOne("Core.Entities.Country", "Country") 196 | .WithMany() 197 | .HasForeignKey("CountryId") 198 | .OnDelete(DeleteBehavior.Cascade); 199 | }); 200 | 201 | modelBuilder.Entity("Core.Entities.FlightInstance", b => 202 | { 203 | b.HasOne("Core.Entities.FlightRoute", "FlightRoute") 204 | .WithMany() 205 | .HasForeignKey("FlightRouteId") 206 | .OnDelete(DeleteBehavior.Cascade); 207 | }); 208 | 209 | modelBuilder.Entity("Core.Entities.FlightRoute", b => 210 | { 211 | b.HasOne("Core.Entities.Airport", "Destination") 212 | .WithMany("Destinations") 213 | .HasForeignKey("DestinationId") 214 | .OnDelete(DeleteBehavior.Restrict); 215 | 216 | b.HasOne("Core.Entities.Airport", "Origin") 217 | .WithMany("Origins") 218 | .HasForeignKey("OriginId") 219 | .OnDelete(DeleteBehavior.Restrict); 220 | }); 221 | 222 | modelBuilder.Entity("Core.Entities.FlightTicket", b => 223 | { 224 | b.HasOne("Core.Entities.FlightInstance", "FlightInstance") 225 | .WithMany() 226 | .HasForeignKey("FlightInstanceId") 227 | .OnDelete(DeleteBehavior.Cascade); 228 | }); 229 | #pragma warning restore 612, 618 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180715201952_ModifyRequestLogEntityRemoveElapsedTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace Infrastructure.Migrations 5 | { 6 | public partial class ModifyRequestLogEntityRemoveElapsedTime : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.DropColumn( 11 | name: "ElapsedTicks", 12 | table: "RequestLog"); 13 | } 14 | 15 | protected override void Down(MigrationBuilder migrationBuilder) 16 | { 17 | migrationBuilder.AddColumn( 18 | name: "ElapsedTicks", 19 | table: "RequestLog", 20 | nullable: false, 21 | defaultValue: new TimeSpan(0, 0, 0, 0, 0)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180715202057_ModifyRequestLogEntityAddElapsedTime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace Infrastructure.Migrations 4 | { 5 | public partial class ModifyRequestLogEntityAddElapsedTime : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.AddColumn( 10 | name: "ElapsedTicks", 11 | table: "RequestLog", 12 | nullable: false, 13 | defaultValue: 0L); 14 | } 15 | 16 | protected override void Down(MigrationBuilder migrationBuilder) 17 | { 18 | migrationBuilder.DropColumn( 19 | name: "ElapsedTicks", 20 | table: "RequestLog"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180715204720_AddDateColumnToRequestLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace Infrastructure.Migrations 5 | { 6 | public partial class AddDateColumnToRequestLog : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.AddColumn( 11 | name: "CreatedAt", 12 | table: "RequestLog", 13 | nullable: false, 14 | defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); 15 | } 16 | 17 | protected override void Down(MigrationBuilder migrationBuilder) 18 | { 19 | migrationBuilder.DropColumn( 20 | name: "CreatedAt", 21 | table: "RequestLog"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/20180718213946_AddApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace Infrastructure.Migrations 5 | { 6 | public partial class AddApplicationUser : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "Users", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false), 15 | UserName = table.Column(nullable: true), 16 | NormalizedUserName = table.Column(nullable: true), 17 | Email = table.Column(nullable: true), 18 | NormalizedEmail = table.Column(nullable: true), 19 | EmailConfirmed = table.Column(nullable: false), 20 | PasswordHash = table.Column(nullable: true), 21 | SecurityStamp = table.Column(nullable: true), 22 | ConcurrencyStamp = table.Column(nullable: true), 23 | PhoneNumber = table.Column(nullable: true), 24 | PhoneNumberConfirmed = table.Column(nullable: false), 25 | TwoFactorEnabled = table.Column(nullable: false), 26 | LockoutEnd = table.Column(nullable: true), 27 | LockoutEnabled = table.Column(nullable: false), 28 | AccessFailedCount = table.Column(nullable: false), 29 | CreatedAt = table.Column(nullable: false), 30 | LastEdited = table.Column(nullable: false) 31 | }, 32 | constraints: table => 33 | { 34 | table.PrimaryKey("PK_Users", x => x.Id); 35 | }); 36 | } 37 | 38 | protected override void Down(MigrationBuilder migrationBuilder) 39 | { 40 | migrationBuilder.DropTable( 41 | name: "Users"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Infrastructure/SqlScripts/linkit_air_data.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertgliguroski/clean-architecture-dot-net-core-angular/30cfb9930d7673032edef41961eab3ee4a4eafd1/Infrastructure/SqlScripts/linkit_air_data.sql -------------------------------------------------------------------------------- /LinkitAir.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinkitAir", "LinkitAir\LinkitAir.csproj", "{10C5ACEC-BC4F-40BD-BD21-BDEE4CBCB926}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{9CA3CF0F-1CB0-48CC-BD83-2C19DF21D141}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{6B465F01-6A19-4DBB-AD8D-875290339316}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {10C5ACEC-BC4F-40BD-BD21-BDEE4CBCB926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {10C5ACEC-BC4F-40BD-BD21-BDEE4CBCB926}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {10C5ACEC-BC4F-40BD-BD21-BDEE4CBCB926}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {10C5ACEC-BC4F-40BD-BD21-BDEE4CBCB926}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9CA3CF0F-1CB0-48CC-BD83-2C19DF21D141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {9CA3CF0F-1CB0-48CC-BD83-2C19DF21D141}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {9CA3CF0F-1CB0-48CC-BD83-2C19DF21D141}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9CA3CF0F-1CB0-48CC-BD83-2C19DF21D141}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {6B465F01-6A19-4DBB-AD8D-875290339316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6B465F01-6A19-4DBB-AD8D-875290339316}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6B465F01-6A19-4DBB-AD8D-875290339316}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6B465F01-6A19-4DBB-AD8D-875290339316}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {271DFE18-ACAE-4F2C-88A5-B72EB7CB0443} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /LinkitAir/.gitignore: -------------------------------------------------------------------------------- 1 | /Properties/launchSettings.json 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | bin/ 25 | Bin/ 26 | obj/ 27 | Obj/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opendb 78 | *.opensdf 79 | *.sdf 80 | *.cachefile 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | *.vspx 86 | *.sap 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | nCrunchTemp_* 112 | 113 | # MightyMoose 114 | *.mm.* 115 | AutoTest.Net/ 116 | 117 | # Web workbench (sass) 118 | .sass-cache/ 119 | 120 | # Installshield output folder 121 | [Ee]xpress/ 122 | 123 | # DocProject is a documentation generator add-in 124 | DocProject/buildhelp/ 125 | DocProject/Help/*.HxT 126 | DocProject/Help/*.HxC 127 | DocProject/Help/*.hhc 128 | DocProject/Help/*.hhk 129 | DocProject/Help/*.hhp 130 | DocProject/Help/Html2 131 | DocProject/Help/html 132 | 133 | # Click-Once directory 134 | publish/ 135 | 136 | # Publish Web Output 137 | *.[Pp]ublish.xml 138 | *.azurePubxml 139 | # TODO: Comment the next line if you want to checkin your web deploy settings 140 | # but database connection strings (with potential passwords) will be unencrypted 141 | *.pubxml 142 | *.publishproj 143 | 144 | # NuGet Packages 145 | *.nupkg 146 | # The packages folder can be ignored because of Package Restore 147 | **/packages/* 148 | # except build/, which is used as an MSBuild target. 149 | !**/packages/build/ 150 | # Uncomment if necessary however generally it will be regenerated when needed 151 | #!**/packages/repositories.config 152 | 153 | # Microsoft Azure Build Output 154 | csx/ 155 | *.build.csdef 156 | 157 | # Microsoft Azure Emulator 158 | ecf/ 159 | rcf/ 160 | 161 | # Microsoft Azure ApplicationInsights config file 162 | ApplicationInsights.config 163 | 164 | # Windows Store app package directory 165 | AppPackages/ 166 | BundleArtifacts/ 167 | 168 | # Visual Studio cache files 169 | # files ending in .cache can be ignored 170 | *.[Cc]ache 171 | # but keep track of directories ending in .cache 172 | !*.[Cc]ache/ 173 | 174 | # Others 175 | ClientBin/ 176 | ~$* 177 | *~ 178 | *.dbmdl 179 | *.dbproj.schemaview 180 | *.pfx 181 | *.publishsettings 182 | orleans.codegen.cs 183 | 184 | /node_modules 185 | 186 | # RIA/Silverlight projects 187 | Generated_Code/ 188 | 189 | # Backup & report files from converting an old project file 190 | # to a newer Visual Studio version. Backup files are not needed, 191 | # because we have git ;-) 192 | _UpgradeReport_Files/ 193 | Backup*/ 194 | UpgradeLog*.XML 195 | UpgradeLog*.htm 196 | 197 | # SQL Server files 198 | *.mdf 199 | *.ldf 200 | 201 | # Business Intelligence projects 202 | *.rdl.data 203 | *.bim.layout 204 | *.bim_*.settings 205 | 206 | # Microsoft Fakes 207 | FakesAssemblies/ 208 | 209 | # GhostDoc plugin setting file 210 | *.GhostDoc.xml 211 | 212 | # Node.js Tools for Visual Studio 213 | .ntvs_analysis.dat 214 | 215 | # Visual Studio 6 build log 216 | *.plg 217 | 218 | # Visual Studio 6 workspace options file 219 | *.opt 220 | 221 | # Visual Studio LightSwitch build output 222 | **/*.HTMLClient/GeneratedArtifacts 223 | **/*.DesktopClient/GeneratedArtifacts 224 | **/*.DesktopClient/ModelManifest.xml 225 | **/*.Server/GeneratedArtifacts 226 | **/*.Server/ModelManifest.xml 227 | _Pvt_Extensions 228 | 229 | # Paket dependency manager 230 | .paket/paket.exe 231 | 232 | # FAKE - F# Make 233 | .fake/ 234 | -------------------------------------------------------------------------------- /LinkitAir/ActionFilterHelpers/RequestActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using System.Diagnostics; 3 | using Core.Interfaces; 4 | using LinkitAir.ViewModels; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace LinkitAir.ActionFilterHelpers 9 | { 10 | public class RequestActionFilter : IActionFilter 11 | { 12 | private const string StopwatchKey = "StopwatchFilter.Value"; 13 | private readonly IRequestLogService _requestLogService; 14 | 15 | public RequestActionFilter(IRequestLogService requestLogService) 16 | { 17 | _requestLogService = requestLogService; 18 | } 19 | 20 | public void OnActionExecuting(ActionExecutingContext context) 21 | { 22 | context.HttpContext.Items[StopwatchKey] = Stopwatch.StartNew(); 23 | var request = context.HttpContext.Request; 24 | /* this RequestLogViewModel has since been deleted, it served no real purpose 25 | requestLogViewModel.RequestMethod = request.Method; 26 | requestLogViewModel.UrlPath = request.Path; */ 27 | } 28 | 29 | public void OnActionExecuted(ActionExecutedContext context) 30 | { 31 | 32 | Stopwatch stopwatch = (Stopwatch)context.HttpContext.Items[StopwatchKey]; 33 | var elapsedTicks = stopwatch.Elapsed.Ticks; 34 | var responseStatusCode = context.HttpContext.Response.StatusCode.ToString(); 35 | /* requestLogViewModel.ResponseStatusCode = responseStatusCode; 36 | requestLogViewModel.ElapsedTicks = elapsedTicks; 37 | 38 | _requestLogService.CreateRequestLog(requestLogViewModel.RequestMethod, requestLogViewModel.ResponseStatusCode, 39 | requestLogViewModel.UrlPath, requestLogViewModel.ElapsedTicks); */ 40 | } 41 | 42 | //public override async Task ExecuteResultAsync(ActionContext context) 43 | //{ 44 | 45 | //} 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "LinkitAir" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets" 12 | ], 13 | "index": "index.html", 14 | "main": "main.ts", 15 | "polyfills": "polyfills.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.app.json", 18 | "testTsconfig": "tsconfig.spec.json", 19 | "prefix": "app", 20 | "styles": [ 21 | "styles.css", 22 | "../node_modules/bootstrap/dist/css/bootstrap.min.css" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts" 29 | } 30 | } 31 | ], 32 | "e2e": { 33 | "protractor": { 34 | "config": "./protractor.conf.js" 35 | } 36 | }, 37 | "lint": [ 38 | { 39 | "project": "src/tsconfig.app.json", 40 | "exclude": "**/node_modules/**" 41 | }, 42 | { 43 | "project": "src/tsconfig.spec.json", 44 | "exclude": "**/node_modules/**" 45 | }, 46 | { 47 | "project": "e2e/tsconfig.e2e.json", 48 | "exclude": "**/node_modules/**" 49 | } 50 | ], 51 | "test": { 52 | "karma": { 53 | "config": "./karma.conf.js" 54 | } 55 | }, 56 | "defaults": { 57 | "styleExt": "css", 58 | "component": {}, 59 | "build": { 60 | "progress": true 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # AngularSpa 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getMainHeading()).toEqual('Hello, world!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getMainHeading() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LinkitAir", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --extract-css", 8 | "build": "ng build --extract-css", 9 | "build:ssr": "npm run build -- --app=ssr --output-hashing=media", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^5.2.0", 17 | "@angular/cdk": "^5.2.5", 18 | "@angular/common": "^5.2.0", 19 | "@angular/compiler": "^5.2.0", 20 | "@angular/core": "^5.2.0", 21 | "@angular/forms": "^5.2.0", 22 | "@angular/http": "^5.2.0", 23 | "@angular/material": "^5.2.5", 24 | "@angular/platform-browser": "^5.2.0", 25 | "@angular/platform-browser-dynamic": "^5.2.0", 26 | "@angular/platform-server": "^5.2.0", 27 | "@angular/router": "^5.2.0", 28 | "@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5", 29 | "aspnet-prerendering": "^3.0.1", 30 | "bootstrap": "^3.3.7", 31 | "core-js": "^2.4.1", 32 | "rxjs": "^5.5.6", 33 | "zone.js": "^0.8.19" 34 | }, 35 | "devDependencies": { 36 | "@angular/cli": "~1.7.0", 37 | "@angular/compiler-cli": "^5.2.0", 38 | "@angular/language-service": "^5.2.0", 39 | "@types/jasmine": "~2.8.3", 40 | "@types/jasminewd2": "~2.0.2", 41 | "@types/node": "~6.0.60", 42 | "codelyzer": "^4.0.1", 43 | "jasmine-core": "~2.8.0", 44 | "jasmine-spec-reporter": "~4.2.1", 45 | "karma": "~2.0.0", 46 | "karma-chrome-launcher": "~2.2.0", 47 | "karma-coverage-istanbul-reporter": "^1.2.1", 48 | "karma-jasmine": "~1.1.0", 49 | "karma-jasmine-html-reporter": "^0.2.2", 50 | "protractor": "~5.1.2", 51 | "ts-node": "~4.1.0", 52 | "tslint": "~5.9.1", 53 | "typescript": "~2.5.3", 54 | "webpack-cli": "^3.0.8", 55 | "webpack-command": "^0.4.1" 56 | }, 57 | "optionalDependencies": { 58 | "node-sass": "^4.9.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts linguist-language=Typescript 2 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/admin/admin.component.css: -------------------------------------------------------------------------------- 1 | h2{ 2 | text-align: center; 3 | } 4 | .card-wrapper { 5 | max-width: 1000px; 6 | width: 100%; 7 | margin: 0 auto; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | } 12 | 13 | .admin-card { 14 | max-width: 300px; 15 | width: 25%; 16 | float: left; 17 | margin-left: 20px; 18 | } 19 | 20 | .status-ok { 21 | background-color: #77dd91; 22 | } 23 | 24 | .status-four { 25 | background-color: #ff8080; 26 | } 27 | 28 | .status-five { 29 | background-color: #ff3434; 30 | } 31 | 32 | .by-code { 33 | background-color: #A9CCE3; 34 | } 35 | 36 | .stats-card { 37 | background-color: #D7BDE2; 38 | } 39 | 40 | .total-requests { 41 | background-color: #565656b3; 42 | color: beige; 43 | } 44 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/admin/admin.component.html: -------------------------------------------------------------------------------- 1 |

Admin panel

2 | 3 | 4 |
5 | 6 | 7 | Total number of requests processed 8 | 9 | 10 | {{requestStats.count}} 11 | 12 | 13 | 14 | 15 | Number of requests resulting in Status: 200 16 | 17 | 18 | {{noOfRequestsByStatus['200']}} 19 | 20 | 21 | 22 | 23 | Number of requests resulting in Status: 4xx 24 | 25 | 26 | {{noOfRequestsStartingWith['4']}} 27 | 28 | 29 | 30 | 31 | Number of requests resulting in Status: 5xx 32 | 33 | 34 | {{noOfRequestsStartingWith['5']}} 35 | 36 | 37 |
38 |
39 | 40 | 41 | Average response time 42 | 43 | 44 | {{requestStats.average}} seconds 45 | 46 | 47 | 48 | 49 | Minimum response time 50 | 51 | 52 | {{requestStats.min}} seconds 53 | 54 | 55 | 56 | 57 | Maximum response time 58 | 59 | 60 | {{requestStats.max}} seconds 61 | 62 | 63 |
64 | 65 |
66 | 67 | 68 | Status: {{stat.responseStatusCode}} 69 | 70 | 71 | {{stat.numberOfRequestPerResponseCode}} requests 72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { StatsService } from '../services/stats-service'; 3 | import { AuthService } from '../services/auth-service'; 4 | import { Router } from "@angular/router"; 5 | 6 | import { Observable } from 'rxjs/Observable'; 7 | 8 | @Component({ 9 | selector: 'app-admin', 10 | templateUrl: './admin.component.html', 11 | styleUrls: ['./admin.component.css'] 12 | }) 13 | export class AdminComponent implements OnInit { 14 | noOfRequestsByStatus: any; 15 | noOfRequestsStartingWith: any; 16 | noOfRequestsByResponseCode: any[]; 17 | requestStats: any; 18 | 19 | constructor(private statsService: StatsService, public auth: AuthService, private router: Router) { 20 | this.noOfRequestsByStatus = { 21 | '200': 0 22 | }; 23 | this.noOfRequestsStartingWith = { 24 | '4': 0, 25 | '5': 0 26 | } 27 | } 28 | 29 | ngOnInit() { 30 | if (!this.auth.isLoggedIn()) { 31 | this.router.navigate(["/login"]); 32 | } else { 33 | this.getNoOfRequestsWithStatus('200'); 34 | this.getNoOfRequestsStartingWithStatus('4'); 35 | this.getNoOfRequestsStartingWithStatus('5'); 36 | this.getNumberOfRequestsByStatusCode(); 37 | this.getRequestStats(); 38 | } 39 | } 40 | 41 | getNoOfRequestsWithStatus(statusCode: string): void { 42 | this.statsService.getNumberOfRequestsWithStatusCode(statusCode).subscribe( 43 | data => { this.noOfRequestsByStatus[statusCode] = data; }, 44 | err => console.log(err) 45 | ); 46 | } 47 | 48 | getNoOfRequestsStartingWithStatus(statusCode: string): void { 49 | this.statsService.getNumberOfRequestsStartingWithStatusCode(statusCode).subscribe( 50 | data => { this.noOfRequestsStartingWith[statusCode] = data; }, 51 | err => console.log(err) 52 | ); 53 | } 54 | 55 | getNumberOfRequestsByStatusCode(): void { 56 | this.statsService.getNumberOfRequestsByStatusCode().subscribe( 57 | data => { this.noOfRequestsByResponseCode = data; console.log('data = '); console.dir(data); }, 58 | err => console.log(err) 59 | ); 60 | } 61 | 62 | getRequestStats(): void { 63 | this.statsService.getRequestStats().subscribe( 64 | data => { this.requestStats = data; }, 65 | err => console.log(err) 66 | ); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/airport/airport-list-component.css: -------------------------------------------------------------------------------- 1 | .airport-list-form, .flight-result-container { 2 | max-width: 1000px; 3 | width: 100%; 4 | margin: 0 auto; 5 | } 6 | 7 | .card-wrapper { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | width: 1000px; 12 | } 13 | 14 | .flight-wrapper{ 15 | margin-top: 20px; 16 | } 17 | 18 | .main-card { 19 | max-width: 300px; 20 | width: 45%; 21 | float: left; 22 | } 23 | 24 | .main-card-toggle { 25 | max-width: 80px; 26 | width: 10%; 27 | float: left; 28 | } 29 | 30 | .flight-result-card{ 31 | max-width: 400px; 32 | width: 40%; 33 | float: left; 34 | } 35 | 36 | .flight-result-card span{ 37 | display: block; 38 | text-align: left; 39 | } 40 | 41 | .flight-result-price-card { 42 | max-width: 120px; 43 | width: 20%; 44 | float: left; 45 | } 46 | 47 | .check-alternative-label{ 48 | margin-top: 20px; 49 | margin-bottom: 20px; 50 | } 51 | 52 | .search-alt-wrapper{ 53 | margin-bottom: 20px; 54 | } 55 | 56 | .main-card.left { 57 | } 58 | 59 | .main-card.right { 60 | } 61 | 62 | .airport-list-field { 63 | /*width: 40%; 64 | margin: 2%; 65 | display: inline-block;*/ 66 | } 67 | 68 | .origin-airport{ 69 | } 70 | 71 | .destination-airport{ 72 | } 73 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/airport/airport-list-component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AirportService } from '../services/airport-service'; 3 | import { FlightService } from '../services/flight-service'; 4 | import { FormControl } from '@angular/forms'; 5 | 6 | import { Observable } from 'rxjs/Observable'; 7 | import { startWith } from 'rxjs/operators/startWith'; 8 | import { map } from 'rxjs/operators/map'; 9 | 10 | @Component({ 11 | selector: 'airport-list', 12 | templateUrl: './airport-list.component.html', 13 | styleUrls: ['./airport-list-component.css'] 14 | }) 15 | 16 | export class AirportListComponent implements OnInit { 17 | originAirports: IAirport[]; 18 | destinationAirports: IAirport[]; 19 | console = console; 20 | selectedOrigin: IAirport; 21 | selectedDestination: IAirport; 22 | flightDate: string; 23 | flightWithDate: IFlight; 24 | flightsOriginDestination: IFlight[]; 25 | alternativeFlights: IFlight[]; 26 | originAirportsCtrl: FormControl; 27 | destinationAirportsCtrl: FormControl; 28 | filteredOriginAirports: Observable; 29 | filteredDestinationAirports: Observable; 30 | returnChecked = false; 31 | wasCheckedReturn = false; 32 | 33 | constructor(private airportService: AirportService, private flightService: FlightService) { 34 | this.originAirportsCtrl = new FormControl(); 35 | this.destinationAirportsCtrl = new FormControl(); 36 | } 37 | 38 | getOriginAirports(): void { 39 | this.airportService.getAirports().subscribe( 40 | data => { this.originAirports = data }, 41 | err => console.log(err) 42 | ); 43 | } 44 | 45 | filterAirports(val: any, airports: any) { 46 | return airports.filter(airport => { 47 | let name = val.name ? val.name : val; 48 | return airport.name.toLowerCase().indexOf(name.toLowerCase()) >= 0 || 49 | airport.code.toLowerCase().indexOf(name.toLowerCase()) >= 0 || 50 | airport.description.toLowerCase().indexOf(name.toLowerCase()) >= 0; 51 | }); 52 | } 53 | 54 | ngOnInit() { 55 | this.airportService.getAirports().subscribe( 56 | data => { 57 | this.originAirports = data; 58 | this.filteredOriginAirports = this.originAirportsCtrl.valueChanges 59 | .pipe( 60 | startWith(''), 61 | map(val => this.filterAirports(val, this.originAirports)) 62 | // map(name => name ? this.filterAirports(name) : this.originAirports.slice()) 63 | ); 64 | }, 65 | err => console.log(err) 66 | ); 67 | } 68 | 69 | onOriginSelected(originAirport) { 70 | this.selectedOrigin = originAirport; 71 | this.selectedDestination = null; 72 | this.airportService.getDestinationAirports(originAirport.id).subscribe( 73 | data => { 74 | this.destinationAirports = data; 75 | this.filteredDestinationAirports = this.destinationAirportsCtrl.valueChanges 76 | .pipe( 77 | startWith(''), 78 | map(val => this.filterAirports(val, this.destinationAirports)) 79 | 80 | ); 81 | }, 82 | err => console.log(err) 83 | ); 84 | } 85 | 86 | onDestinationSelected(destinationAirport) { 87 | this.selectedDestination = destinationAirport; 88 | } 89 | 90 | displayFn(airport: any): string { 91 | return airport ? airport.name : airport; 92 | } 93 | 94 | 95 | getFlightWithDate() { 96 | this.flightService.getFlightForOriginAndDestinationAndDate(this.selectedOrigin, this.selectedDestination, this.flightDate).subscribe( 97 | data => { this.flightWithDate = data }, 98 | err => console.log(err) 99 | ); 100 | } 101 | 102 | searchFlightsOriginDestination() { 103 | 104 | return this.flightService.getFlightsForOriginAndDestination(this.selectedOrigin.id, 105 | this.selectedDestination.id, this.returnChecked).subscribe( 106 | data => { 107 | this.flightsOriginDestination = data; 108 | this.alternativeFlights = []; 109 | this.wasCheckedReturn = this.returnChecked; 110 | }, 111 | err => console.log(err) 112 | ); 113 | } 114 | 115 | searchAlternativeFlights(originalResultFlightInstanceId: number) { 116 | 117 | return this.flightService.getAlternativeFlightsForOriginAndDestination(this.selectedOrigin.id, 118 | this.selectedDestination.id, originalResultFlightInstanceId, this.returnChecked).subscribe( 119 | data => { this.alternativeFlights = data; }, 120 | err => console.log(err) 121 | ); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/airport/airport-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 7 | 8 | 9 | {{ originAirport.name }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | {{ destinationAirport.name }} 21 | 22 | 23 | 24 | 25 |
26 | Return ticket 27 |
28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 37 | 38 | 39 |
40 |
41 | 42 | Flight code: {{flight.flightCode}} 43 | From: {{flight.originAirportName}}, {{flight.originCityName}} 44 | Departure: {{flight.departureTime}} 45 | 46 | 47 | To: {{flight.destinationAirportName}}, {{flight.destinationCityName}} 48 | Arrival: {{flight.arrivalTime}} 49 | 50 | 51 | Price: {{flight.price}} 52 | 53 |
54 |
55 | We're sorry, but there aren't any return flights for the selected combination 56 |
57 | 58 |
59 | Check these alternative flights from/to other airports in the selected cities:
60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 | Flight code: {{flight.flightCode}} 70 | From: {{flight.originAirportName}}, {{flight.originCityName}} 71 | Departure: {{flight.departureTime}} 72 | 73 | 74 | To: {{flight.destinationAirportName}}, {{flight.destinationCityName}} 75 | Arrival: {{flight.arrivalTime}} 76 | 77 | 78 | Price: {{flight.price}} 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ 3 | .body-content { 4 | padding-top: 50px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 13 | 19 | 25 | 26 | 27 | LinkitAir 28 | 29 | 30 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from "@angular/router"; 3 | import { AuthService } from './services/auth-service'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | title = 'app'; 12 | contextRoute: string; 13 | 14 | constructor(public auth: AuthService, public router: Router) { 15 | } 16 | 17 | logout(): boolean { 18 | if (this.auth.logout()) { 19 | this.router.navigate([""]); 20 | } 21 | return false; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 7 | import { RouterModule } from '@angular/router'; 8 | import { 9 | MatFormFieldModule, 10 | MatAutocompleteModule, 11 | MatInputModule, 12 | MatCardModule, 13 | MatToolbarModule, 14 | MatButtonModule, 15 | MatCheckboxModule, 16 | MatIconModule, 17 | MatMenuModule, 18 | MatSlideToggleModule, 19 | MatListModule 20 | } from '@angular/material'; 21 | 22 | import { AppComponent } from './app.component'; 23 | import { NavMenuComponent } from './nav-menu/nav-menu.component'; 24 | import { HomeComponent } from './home/home.component'; 25 | import { AirportListComponent } from './airport/airport-list-component'; 26 | import { AdminComponent } from './admin/admin.component'; 27 | import { LoginComponent } from './login/login.component'; 28 | import { BaseService } from './services/base-service'; 29 | import { AirportService } from './services/airport-service'; 30 | import { FlightService } from './services/flight-service'; 31 | import { StatsService } from './services/stats-service'; 32 | import { AuthService } from './services/auth-service'; 33 | import { AuthInterceptor } from './services/auth-interceptor-service' 34 | 35 | @NgModule({ 36 | declarations: [ 37 | AppComponent, 38 | NavMenuComponent, 39 | HomeComponent, 40 | AirportListComponent, 41 | AdminComponent, 42 | LoginComponent 43 | ], 44 | imports: [ 45 | BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), 46 | BrowserAnimationsModule, 47 | HttpClientModule, 48 | FormsModule, 49 | ReactiveFormsModule, 50 | MatFormFieldModule, 51 | MatAutocompleteModule, 52 | MatInputModule, 53 | MatCardModule, 54 | MatToolbarModule, 55 | MatButtonModule, 56 | MatCheckboxModule, 57 | MatIconModule, 58 | MatMenuModule, 59 | MatSlideToggleModule, 60 | MatListModule, 61 | RouterModule.forRoot([ 62 | { path: '', component: HomeComponent, pathMatch: 'full' }, 63 | { path: 'admin', component: AdminComponent, pathMatch: 'full' }, 64 | { path: 'login', component: LoginComponent, pathMatch: 'full' } 65 | 66 | ]) 67 | ], 68 | providers: [ 69 | { provide: 'BASE_URL', useFactory: getBaseUrl }, 70 | BaseService, 71 | AirportService, 72 | FlightService, 73 | StatsService, 74 | AuthService, 75 | { 76 | provide: HTTP_INTERCEPTORS, 77 | useClass: AuthInterceptor, 78 | multi: true 79 | } 80 | ], 81 | bootstrap: [AppComponent] 82 | }) 83 | export class AppModule { } 84 | 85 | export function getBaseUrl() { 86 | return document.getElementsByTagName('base')[0].href; 87 | } 88 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | }) 7 | export class HomeComponent { 8 | } 9 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/airport.js: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=airport.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/airport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"airport.js","sourceRoot":"","sources":["airport.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/airport.ts: -------------------------------------------------------------------------------- 1 | interface IAirport { 2 | id: number, 3 | code: string, 4 | name: string, 5 | description: string, 6 | cityId: number 7 | } 8 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/flight.js: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=flight.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/flight.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"flight.js","sourceRoot":"","sources":["flight.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/flight.ts: -------------------------------------------------------------------------------- 1 | interface IFlight { 2 | flightInstanceId: number, 3 | flightCode: string, 4 | originAirportName: string, 5 | destinationAirportName: string, 6 | originCityName: string, 7 | destinationCityName: string, 8 | departureTime: DateTimeFormat, 9 | arrivalTime: DateTimeFormat, 10 | price: number 11 | } 12 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/interfaces/token-response.ts: -------------------------------------------------------------------------------- 1 | interface TokenResponse { 2 | token: string, 3 | expiration: number 4 | } 5 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .main-card { 2 | max-width: 400px; 3 | } 4 | 5 | .title { 6 | width: 50%; 7 | margin: auto; 8 | color: #3f51b5; 9 | } 10 | 11 | .mat-card-header-text{ 12 | margin: auto !important; 13 | } 14 | 15 | .submit-btn{ 16 | display: block; 17 | margin: auto; 18 | } 19 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Admin Login 4 |
5 | 6 |
7 | 8 | 9 | Please insert a valid username or e-mail address. 10 | 11 | 12 | 13 | Please insert a password. 14 | 15 | 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/login/login.component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=login.component.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/login/login.component.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"login.component.js","sourceRoot":"","sources":["login.component.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from "@angular/core"; 2 | import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms'; 3 | import { Router } from "@angular/router"; 4 | import { AuthService } from '../services/auth-service'; 5 | 6 | @Component({ 7 | selector: "login", 8 | templateUrl: "./login.component.html", 9 | styleUrls: ['./login.component.css'] 10 | }) 11 | export class LoginComponent { 12 | form: FormGroup; 13 | 14 | constructor(private router: Router, 15 | private fb: FormBuilder, 16 | private authService: AuthService, 17 | @Inject('BASE_URL') private baseUrl: string) { 18 | // initialize the form 19 | this.createForm(); 20 | } 21 | 22 | createForm() { 23 | this.form = this.fb.group({ 24 | Username: ['', Validators.required], 25 | Password: ['', Validators.required] 26 | }); 27 | } 28 | 29 | onSubmit() { 30 | var url = this.baseUrl + "api/token/auth"; 31 | var username = this.form.value.Username; 32 | var password = this.form.value.Password; 33 | this.authService.login(username, password) 34 | .subscribe(res => { 35 | this.router.navigate(["admin"]); 36 | }, 37 | err => { 38 | // login failed 39 | console.log(err) 40 | this.form.setErrors({ 41 | "auth": "Incorrect username or password" 42 | }); 43 | }); 44 | } 45 | 46 | 47 | onBack() { 48 | this.router.navigate(["home"]); 49 | } 50 | 51 | getFormControl(name: string) { 52 | return this.form.get(name); 53 | } 54 | // returns TRUE if the FormControl is valid 55 | isValid(name: string) { 56 | var e = this.getFormControl(name); 57 | return e && e.valid; 58 | } 59 | // returns TRUE if the FormControl has been changed 60 | isChanged(name: string) { 61 | var e = this.getFormControl(name); 62 | return e && (e.dirty || e.touched); 63 | } 64 | // returns TRUE if the FormControl is invalid after user changes 65 | hasError(name: string) { 66 | var e = this.getFormControl(name); 67 | return e && (e.dirty || e.touched) && !e.valid; 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/nav-menu/nav-menu.component.css: -------------------------------------------------------------------------------- 1 | li .glyphicon { 2 | margin-right: 10px; 3 | } 4 | 5 | /* Highlighting rules for nav menu items */ 6 | li.link-active a, 7 | li.link-active a:hover, 8 | li.link-active a:focus { 9 | background-color: #4189C7; 10 | color: white; 11 | } 12 | 13 | /* Keep the nav menu independent of scrolling and on top of other items */ 14 | .main-nav { 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | z-index: 1; 20 | } 21 | 22 | @media (min-width: 768px) { 23 | /* On small screens, convert the nav menu to a vertical sidebar */ 24 | .main-nav { 25 | height: 100%; 26 | width: calc(25% - 20px); 27 | } 28 | .navbar { 29 | border-radius: 0px; 30 | border-width: 0px; 31 | height: 100%; 32 | } 33 | .navbar-header { 34 | float: none; 35 | } 36 | .navbar-collapse { 37 | border-top: 1px solid #444; 38 | padding: 0px; 39 | } 40 | .navbar ul { 41 | float: none; 42 | } 43 | .navbar li { 44 | float: none; 45 | font-size: 15px; 46 | margin: 6px; 47 | } 48 | .navbar li a { 49 | padding: 10px 16px; 50 | border-radius: 4px; 51 | } 52 | .navbar a { 53 | /* If a menu item's text is too long, truncate it */ 54 | width: 100%; 55 | white-space: nowrap; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/nav-menu/nav-menu.component.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/nav-menu/nav-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-nav-menu', 5 | templateUrl: './nav-menu.component.html', 6 | styleUrls: ['./nav-menu.component.css'] 7 | }) 8 | export class NavMenuComponent { 9 | isExpanded = false; 10 | 11 | collapse() { 12 | this.isExpanded = false; 13 | } 14 | 15 | toggle() { 16 | this.isExpanded = !this.isExpanded; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/airport-service.js: -------------------------------------------------------------------------------- 1 | //import { Injectable } from '@angular/core'; 2 | //import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | //import { Observable } from 'rxjs'; 4 | //import { _throw } from 'rxjs/observable/throw'; 5 | //import { catchError, tap, map } from 'rxjs/operators'; 6 | //@Injectable({ 7 | // providedIn: 'root' 8 | //}) 9 | //export class AirportService { 10 | // constructor(private http: HttpClient) { } 11 | // getAirports(): Observable { 12 | // } 13 | //} 14 | //# sourceMappingURL=airport-service.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/airport-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"airport-service.js","sourceRoot":"","sources":["airport-service.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,uEAAuE;AACvE,oCAAoC;AACpC,iDAAiD;AACjD,wDAAwD;AAExD,eAAe;AACf,sBAAsB;AACtB,IAAI;AAEJ,+BAA+B;AAE/B,6CAA6C;AAE7C,2CAA2C;AAE3C,KAAK;AAGL,GAAG"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/airport-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { _throw } from 'rxjs/observable/throw'; 5 | import { catchError, tap, map } from 'rxjs/operators'; 6 | import { BaseService } from './base-service'; 7 | 8 | @Injectable() 9 | 10 | export class AirportService extends BaseService { 11 | 12 | 13 | constructor(private http: HttpClient) { 14 | super(); 15 | } 16 | 17 | getAirports(): Observable { 18 | var url = this.baseApiUrl + this.getAirportsUrl; 19 | return this.http.get(url); 20 | } 21 | 22 | getDestinationAirports(originAirport): Observable { 23 | var url = this.baseApiUrl + this.getDestinationAirportsUrl + originAirport; 24 | return this.http.get(url); 25 | } 26 | 27 | errorHandler(error: Response) { 28 | console.log(error); 29 | return Observable.throw(error); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-interceptor-service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var auth_service_1 = require("./auth-service"); 4 | var AuthInterceptor = /** @class */ (function () { 5 | function AuthInterceptor(injector) { 6 | this.injector = injector; 7 | } 8 | AuthInterceptor.prototype.intercept = function (request, next) { 9 | var auth = this.injector.get(auth_service_1.AuthService); 10 | var token = (auth.isLoggedIn()) ? auth.getAuth().token : null; 11 | if (token) { 12 | request = request.clone({ 13 | setHeaders: { 14 | Authorization: "Bearer " + token 15 | } 16 | }); 17 | } 18 | return next.handle(request); 19 | }; 20 | return AuthInterceptor; 21 | }()); 22 | exports.AuthInterceptor = AuthInterceptor; 23 | //# sourceMappingURL=auth-interceptor-service.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-interceptor-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"auth-interceptor-service.js","sourceRoot":"","sources":["auth-interceptor-service.ts"],"names":[],"mappings":";;AAEA,+CAA6C;AAI7C;IAEE,yBAAoB,QAAkB;QAAlB,aAAQ,GAAR,QAAQ,CAAU;IAAI,CAAC;IAE3C,mCAAS,GAAT,UAAU,OAAyB,EAAE,IAAiB;QACpD,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,0BAAW,CAAC,CAAC;QAC1C,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,IAAI,KAAK,EAAE;YACT,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;gBACtB,UAAU,EAAE;oBACV,aAAa,EAAE,YAAU,KAAO;iBACjC;aACF,CAAC,CAAC;SACJ;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IACH,sBAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,0CAAe"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-interceptor-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from "@angular/core"; 2 | import { HttpHandler, HttpEvent, HttpInterceptor, HttpRequest } from "@angular/common/http"; 3 | import { AuthService } from "./auth-service"; 4 | import { Observable } from "rxjs/Observable"; 5 | 6 | @Injectable() 7 | export class AuthInterceptor implements HttpInterceptor { 8 | 9 | constructor(private injector: Injector) { } 10 | 11 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 12 | var auth = this.injector.get(AuthService); 13 | var token = (auth.isLoggedIn()) ? auth.getAuth()!.token : null; 14 | if (token) { 15 | request = request.clone({ 16 | setHeaders: { 17 | Authorization: `Bearer ${token}` 18 | } 19 | }); 20 | } 21 | return next.handle(request); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | require("rxjs/Rx"); 4 | //# sourceMappingURL=auth-service.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"auth-service.js","sourceRoot":"","sources":["auth-service.ts"],"names":[],"mappings":";;AAIA,mBAAiB"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/auth-service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Inject, Injectable, PLATFORM_ID } from "@angular/core"; 2 | import { isPlatformBrowser } from '@angular/common'; 3 | import { HttpClient, HttpHeaders } from "@angular/common/http"; 4 | import { Observable } from "rxjs"; 5 | import 'rxjs/Rx'; 6 | import { BaseService } from "./base-service"; 7 | 8 | @Injectable() 9 | export class AuthService extends BaseService { 10 | authKey: string = "auth"; 11 | clientId: string = "TestMakerFree"; 12 | 13 | constructor(private http: HttpClient, @Inject(PLATFORM_ID) private platformId: any) { 14 | super(); 15 | } 16 | 17 | login(username: string, password: string): Observable { 18 | var url = this.baseApiUrl + this.loginUrl; 19 | var data = { 20 | username: username, 21 | password: password, 22 | client_id: this.clientId, 23 | // required when signing up with username/password 24 | grant_type: "password", 25 | // space-separated list of scopes for which the token is issued 26 | scope: "offline_access profile email" 27 | }; 28 | return this.http.post(url, data) 29 | .map((res) => { 30 | let token = res && res.token; 31 | // if the token is there, login has been successful 32 | if (token) { 33 | // store username and jwt token 34 | this.setAuth(res); 35 | // successful login 36 | return true; 37 | } 38 | // failed login 39 | return Observable.throw('Unauthorized'); 40 | }) 41 | .catch(error => { 42 | return new Observable(error); 43 | }); 44 | } 45 | 46 | // performs the logout 47 | logout(): boolean { 48 | this.setAuth(null); 49 | return true; 50 | } 51 | 52 | // Persist auth into localStorage or removes it if a NULL argument is given 53 | setAuth(auth: TokenResponse | null): boolean { 54 | if (isPlatformBrowser(this.platformId)) { 55 | if (auth) { 56 | localStorage.setItem( 57 | this.authKey, 58 | JSON.stringify(auth)); 59 | } 60 | else { 61 | localStorage.removeItem(this.authKey); 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | // Retrieves the auth JSON object (or NULL if none) 68 | getAuth(): TokenResponse | null { 69 | if (isPlatformBrowser(this.platformId)) { 70 | var i = localStorage.getItem(this.authKey); 71 | if (i) { 72 | return JSON.parse(i); 73 | } 74 | } 75 | return null; 76 | } 77 | 78 | // Returns TRUE if the user is logged in, FALSE otherwise. 79 | isLoggedIn(): boolean { 80 | if (isPlatformBrowser(this.platformId)) { 81 | return localStorage.getItem(this.authKey) != null; 82 | } 83 | return false; 84 | } 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/base-service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var environment_1 = require("../../environments/environment"); 4 | var BaseService = /** @class */ (function () { 5 | function BaseService() { 6 | this.baseApiUrl = environment_1.environment.baseApiUrl; 7 | this.getAirportsUrl = environment_1.environment.getAirportsUrl; 8 | this.getDestinationAirportsUrl = environment_1.environment.getDestinationAirportsUrl; 9 | this.loginUrl = environment_1.environment.loginUrl; 10 | this.getFlightForOriginDestinationDateUrl = environment_1.environment.getFlightForOriginDestinationDateUrl; 11 | this.getFlightsOriginDestinationUrl = environment_1.environment.getFlightsOriginDestinationUrl; 12 | this.getAlternativeFlightsOriginDestinationUrl = environment_1.environment.getAlternativeFlightsOriginDestinationUrl; 13 | this.getNumberOfRequestsWithStatusCodeUrl = environment_1.environment.getNumberOfRequestsWithStatusCodeUrl; 14 | this.getNumberOfRequestsStartingWithStatusCodeUrl = environment_1.environment.getNumberOfRequestsStartingWithStatusCodeUrl; 15 | this.getRequestStatsUrl = environment_1.environment.getRequestStatsUrl; 16 | this.getNumberOfRequestsByStatusCodeUrl = environment_1.environment.getNumberOfRequestsByStatusCodeUrl; 17 | } 18 | return BaseService; 19 | }()); 20 | exports.BaseService = BaseService; 21 | //# sourceMappingURL=base-service.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/base-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"base-service.js","sourceRoot":"","sources":["base-service.ts"],"names":[],"mappings":";;AAAA,8DAA6D;AAC7D;IAcE;QAZA,eAAU,GAAG,yBAAW,CAAC,UAAU,CAAC;QACpC,mBAAc,GAAG,yBAAW,CAAC,cAAc,CAAC;QAC5C,8BAAyB,GAAG,yBAAW,CAAC,yBAAyB,CAAC;QAClE,aAAQ,GAAG,yBAAW,CAAC,QAAQ,CAAC;QAChC,yCAAoC,GAAG,yBAAW,CAAC,oCAAoC,CAAC;QACxF,mCAA8B,GAAG,yBAAW,CAAC,8BAA8B,CAAC;QAC5E,8CAAyC,GAAG,yBAAW,CAAC,yCAAyC,CAAC;QAClG,yCAAoC,GAAG,yBAAW,CAAC,oCAAoC,CAAC;QACxF,iDAA4C,GAAG,yBAAW,CAAC,4CAA4C,CAAC;QACxG,uBAAkB,GAAG,yBAAW,CAAC,kBAAkB,CAAC;QACpD,uCAAkC,GAAG,yBAAW,CAAC,kCAAkC,CAAC;IAEpE,CAAC;IAEnB,kBAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,kCAAW"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/base-service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from '../../environments/environment'; 2 | export class BaseService { 3 | 4 | baseApiUrl = environment.baseApiUrl; 5 | getAirportsUrl = environment.getAirportsUrl; 6 | getDestinationAirportsUrl = environment.getDestinationAirportsUrl; 7 | loginUrl = environment.loginUrl; 8 | getFlightForOriginDestinationDateUrl = environment.getFlightForOriginDestinationDateUrl; 9 | getFlightsOriginDestinationUrl = environment.getFlightsOriginDestinationUrl; 10 | getAlternativeFlightsOriginDestinationUrl = environment.getAlternativeFlightsOriginDestinationUrl; 11 | getNumberOfRequestsWithStatusCodeUrl = environment.getNumberOfRequestsWithStatusCodeUrl; 12 | getNumberOfRequestsStartingWithStatusCodeUrl = environment.getNumberOfRequestsStartingWithStatusCodeUrl; 13 | getRequestStatsUrl = environment.getRequestStatsUrl; 14 | getNumberOfRequestsByStatusCodeUrl = environment.getNumberOfRequestsByStatusCodeUrl; 15 | 16 | constructor() { } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/flight-service.js: -------------------------------------------------------------------------------- 1 | //import { Injectable } from '@angular/core'; 2 | //import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | //import { Observable } from 'rxjs'; 4 | //import { _throw } from 'rxjs/observable/throw'; 5 | //import { catchError, tap, map } from 'rxjs/operators'; 6 | //@Injectable({ 7 | // providedIn: 'root' 8 | //}) 9 | //export class AirportService { 10 | // constructor(private http: HttpClient) { } 11 | // getAirports(): Observable { 12 | // } 13 | //} 14 | //# sourceMappingURL=airport-service.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/flight-service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"airport-service.js","sourceRoot":"","sources":["airport-service.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,uEAAuE;AACvE,oCAAoC;AACpC,iDAAiD;AACjD,wDAAwD;AAExD,eAAe;AACf,sBAAsB;AACtB,IAAI;AAEJ,+BAA+B;AAE/B,6CAA6C;AAE7C,2CAA2C;AAE3C,KAAK;AAGL,GAAG"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/flight-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { _throw } from 'rxjs/observable/throw'; 5 | import { catchError, tap, map } from 'rxjs/operators'; 6 | import { BaseService } from './base-service'; 7 | 8 | @Injectable() 9 | 10 | export class FlightService extends BaseService{ 11 | 12 | constructor(private http: HttpClient) { 13 | super(); 14 | } 15 | 16 | getFlightForOriginAndDestinationAndDate(originAirport, destinationAirport, flightDate): Observable { 17 | var url = this.baseApiUrl + this.getFlightForOriginDestinationDateUrl + originAirport 18 | + "/destination/" + destinationAirport + "/flightdate/" + flightDate; 19 | return this.http.get(url); 20 | } 21 | 22 | getFlightsForOriginAndDestination(originAirport, destinationAirport, returnTicket): Observable { 23 | var url = this.baseApiUrl + this.getFlightsOriginDestinationUrl + originAirport 24 | + "/destination/" + destinationAirport + "/return/" + returnTicket; 25 | return this.http.get(url); 26 | } 27 | 28 | getAlternativeFlightsForOriginAndDestination(originAirport, destinationAirport, originalResultFlightInstanceId, returnTicket): Observable { 29 | var altUrl = this.baseApiUrl + this.getAlternativeFlightsOriginDestinationUrl + originAirport 30 | + "/destination/" + destinationAirport + "/without/" + originalResultFlightInstanceId + "/return/" + returnTicket; 31 | 32 | return this.http.get(altUrl); 33 | } 34 | 35 | 36 | errorHandler(error: Response) { 37 | console.log(error); 38 | return Observable.throw(error); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/app/services/stats-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { _throw } from 'rxjs/observable/throw'; 5 | import { catchError, tap, map } from 'rxjs/operators'; 6 | import { BaseService } from './base-service'; 7 | 8 | @Injectable() 9 | 10 | export class StatsService extends BaseService { 11 | 12 | constructor(private http: HttpClient) { 13 | super(); 14 | } 15 | 16 | getNumberOfRequestsWithStatusCode(statusCode: string): Observable { 17 | var url = this.baseApiUrl + this.getNumberOfRequestsWithStatusCodeUrl + statusCode; 18 | return this.http.get(url); 19 | } 20 | 21 | getNumberOfRequestsStartingWithStatusCode(statusCode: string): Observable { 22 | var url = this.baseApiUrl + this.getNumberOfRequestsStartingWithStatusCodeUrl + statusCode; 23 | return this.http.get(url); 24 | } 25 | 26 | getNumberOfRequestsByStatusCode(): Observable { 27 | var url = this.baseApiUrl + this.getNumberOfRequestsByStatusCodeUrl; 28 | return this.http.get(url); 29 | } 30 | 31 | getRequestStats(): Observable { 32 | var url = this.baseApiUrl + this.getRequestStatsUrl; 33 | return this.http.get(url); 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertgliguroski/clean-architecture-dot-net-core-angular/30cfb9930d7673032edef41961eab3ee4a4eafd1/LinkitAir/ClientApp/src/assets/.gitkeep -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/environments/environment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // The file contents for the current environment will overwrite these during build. 3 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 4 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 5 | // The list of which env maps to which file can be found in `.angular-cli.json`. 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | exports.environment = { 8 | production: false, 9 | baseApiUrl: 'http://localhost:51736/api/', 10 | getAirportsUrl: 'airport/getairports/', 11 | getDestinationAirportsUrl: 'airport/getdestinationairports/', 12 | loginUrl: 'token/jwt/', 13 | getFlightForOriginDestinationDateUrl: 'flight/GetFlightWithDate/origin/', 14 | getFlightsOriginDestinationUrl: 'flight/GetFlights/origin/', 15 | getAlternativeFlightsOriginDestinationUrl: 'flight/GetAlternativeFlights/origin/', 16 | getNumberOfRequestsWithStatusCodeUrl: 'admin/CountRequestsWithCode/', 17 | getNumberOfRequestsStartingWithStatusCodeUrl: 'admin/CountRequestsStartingWith/', 18 | getRequestStatsUrl: 'admin/GetRequestStats/', 19 | getNumberOfRequestsByStatusCodeUrl: 'admin/CountRequestsByResponseCode/' 20 | }; 21 | //# sourceMappingURL=environment.js.map -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/environments/environment.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"environment.js","sourceRoot":"","sources":["environment.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,8FAA8F;AAC9F,yEAAyE;AACzE,gFAAgF;;AAEnE,QAAA,WAAW,GAAG;IACzB,UAAU,EAAE,KAAK;IACjB,UAAU,EAAE,6BAA6B;IACzC,cAAc,EAAE,sBAAsB;IACtC,yBAAyB,EAAE,iCAAiC;IAC5D,QAAQ,EAAE,YAAY;IACtB,oCAAoC,EAAE,kCAAkC;IACxE,8BAA8B,EAAE,2BAA2B;IAC3D,yCAAyC,EAAE,sCAAsC;IACjF,oCAAoC,EAAE,8BAA8B;IACpE,4CAA4C,EAAE,kCAAkC;IAChF,kBAAkB,EAAE,wBAAwB;IAC5C,kCAAkC,EAAE,oCAAoC;CACzE,CAAC"} -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseApiUrl: '' 4 | }; 5 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | baseApiUrl: 'http://localhost:51736/api/', 9 | getAirportsUrl: 'airport/getairports/', 10 | getDestinationAirportsUrl: 'airport/getdestinationairports/', 11 | loginUrl: 'token/jwt/', 12 | getFlightForOriginDestinationDateUrl: 'flight/GetFlightWithDate/origin/', 13 | getFlightsOriginDestinationUrl: 'flight/GetFlights/origin/', 14 | getAlternativeFlightsOriginDestinationUrl: 'flight/GetAlternativeFlights/origin/', 15 | getNumberOfRequestsWithStatusCodeUrl: 'admin/CountRequestsWithCode/', 16 | getNumberOfRequestsStartingWithStatusCodeUrl: 'admin/CountRequestsStartingWith/', 17 | getRequestStatsUrl: 'admin/GetRequestStats/', 18 | getNumberOfRequestsByStatusCodeUrl: 'admin/CountRequestsByResponseCode/' 19 | }; 20 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LinkitAir 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Loading... 14 | 15 | 16 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | export function getBaseUrl() { 8 | return document.getElementsByTagName('base')[0].href; 9 | } 10 | 11 | const providers = [ 12 | { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } 13 | ]; 14 | 15 | if (environment.production) { 16 | enableProdMode(); 17 | } 18 | 19 | platformBrowserDynamic(providers).bootstrapModule(AppModule) 20 | .catch(err => console.log(err)); 21 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/styles.css: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/prebuilt-themes/indigo-pink.css'; 2 | 3 | body { 4 | margin: 0; 5 | font-family: Roboto, sans-serif; 6 | } 7 | 8 | mat-card { 9 | max-width: 80%; 10 | margin: 2em auto; 11 | text-align: center; 12 | } 13 | 14 | mat-toolbar-row { 15 | justify-content: space-between; 16 | } 17 | 18 | .fill-remaining-space { 19 | /* This fills the remaining space, by using flexbox. 20 | Every toolbar row uses a flexbox row layout. */ 21 | flex: 1 1 auto; 22 | } 23 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "allowJs": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LinkitAir/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs", 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": [ 26 | true, 27 | "spaces" 28 | ], 29 | "interface-over-type-literal": true, 30 | "label-position": true, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-access": false, 36 | "member-ordering": [ 37 | true, 38 | { 39 | "order": [ 40 | "static-field", 41 | "instance-field", 42 | "static-method", 43 | "instance-method" 44 | ] 45 | } 46 | ], 47 | "no-arg": true, 48 | "no-bitwise": true, 49 | "no-console": [ 50 | true, 51 | "debug", 52 | "info", 53 | "time", 54 | "timeEnd", 55 | "trace" 56 | ], 57 | "no-construct": true, 58 | "no-debugger": true, 59 | "no-duplicate-super": true, 60 | "no-empty": false, 61 | "no-empty-interface": true, 62 | "no-eval": true, 63 | "no-inferrable-types": [ 64 | true, 65 | "ignore-params" 66 | ], 67 | "no-misused-new": true, 68 | "no-non-null-assertion": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "directive-selector": [ 121 | true, 122 | "attribute", 123 | "app", 124 | "camelCase" 125 | ], 126 | "component-selector": [ 127 | true, 128 | "element", 129 | "app", 130 | "kebab-case" 131 | ], 132 | "no-output-on-prefix": true, 133 | "use-input-property-decorator": true, 134 | "use-output-property-decorator": true, 135 | "use-host-property-decorator": true, 136 | "no-input-rename": true, 137 | "no-output-rename": true, 138 | "use-life-cycle-interface": true, 139 | "use-pipe-transform-interface": true, 140 | "component-class-suffix": true, 141 | "directive-class-suffix": true 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /LinkitAir/Controllers/AdminController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Core.Interfaces; 3 | using Mapster; 4 | using LinkitAir.ViewModels; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.Extensions.Configuration; 7 | using Core.Entities; 8 | using Microsoft.AspNetCore.Authorization; 9 | 10 | namespace LinkitAir.Controllers 11 | { 12 | [Route("api/[controller]")] 13 | [Authorize] 14 | public class AdminController : BaseController 15 | { 16 | private readonly IRequestLogService _requestLogService; 17 | 18 | public AdminController(IRequestLogService requestLogService, RoleManager roleManager, 19 | UserManager userManager, IConfiguration configuration) 20 | : base(roleManager, userManager, configuration) 21 | { 22 | _requestLogService = requestLogService; 23 | } 24 | 25 | /// 26 | /// Counts all the processed requests 27 | /// 28 | /// 29 | /// Returns the total number of requests processed by the application 30 | /// 31 | [HttpGet("[action]")] 32 | public IActionResult CountRequests() 33 | { 34 | var count = _requestLogService.GetTotalNumberOfRequestsProcessed(); 35 | return new JsonResult(count); 36 | } 37 | 38 | [HttpGet("[action]")] 39 | public IActionResult CountRequestsByResponseCode() 40 | { 41 | var countGroups = _requestLogService.GetNumberOfRequestsByResponseCode(); 42 | return new JsonResult(countGroups); 43 | } 44 | 45 | [HttpGet("[action]/{code}")] 46 | public IActionResult CountRequestsStartingWith(string code) 47 | { 48 | var count = _requestLogService.GetTotalNumberOfRequestsWithResponseCodeStartingWith(code); 49 | return new JsonResult(count); 50 | } 51 | 52 | [HttpGet("[action]/{code}")] 53 | public IActionResult CountRequestsWithCode(string code) 54 | { 55 | var count = _requestLogService.GetTotalNumberOfRequestsWithResponseCode(code); 56 | return new JsonResult(count); 57 | } 58 | 59 | [HttpGet("[action]")] 60 | public IActionResult GetRequestStats() 61 | { 62 | var results = _requestLogService.GetStats(); 63 | return new JsonResult(results); 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /LinkitAir/Controllers/AirportController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Core.Interfaces; 3 | using Mapster; 4 | using LinkitAir.ViewModels; 5 | 6 | namespace LinkitAir.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | public class AirportController : Controller 10 | { 11 | private readonly IAirportService _airportService; 12 | 13 | public AirportController(IAirportService airportService) 14 | { 15 | _airportService = airportService; 16 | } 17 | 18 | /// 19 | /// Returns all the airport records 20 | /// 21 | /// 22 | /// Lists all the airport records in our database, without any filtering 23 | /// 24 | /// OK 25 | [HttpGet("[action]")] 26 | public IActionResult GetAirports() 27 | { 28 | var airports = _airportService.GetAirports(); 29 | return new JsonResult(airports.Adapt()); 30 | } 31 | 32 | /// 33 | /// Returns all the destination airports for this origin 34 | /// 35 | /// 36 | /// Lists all the airports that the selected airports has flights to 37 | /// 38 | /// 39 | /// OK 40 | [HttpGet("[action]/{originAirportId}")] 41 | public IActionResult GetDestinationAirports(int originAirportId) 42 | { 43 | var airports = _airportService.GetDestinationAirportsForOriginAirport(originAirportId); 44 | return new JsonResult(airports.Adapt()); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LinkitAir/Controllers/BaseController.cs: -------------------------------------------------------------------------------- 1 | using LinkitAir.ActionFilterHelpers; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.Extensions.Configuration; 5 | using Core.Entities; 6 | 7 | 8 | namespace LinkitAir.Controllers 9 | { 10 | // [ServiceFilter(typeof(RequestActionFilter))] 11 | public class BaseController : Controller 12 | { 13 | protected RoleManager RoleManager { get; private set; } 14 | protected UserManager UserManager { get; private set; } 15 | protected IConfiguration Configuration { get; private set; } 16 | 17 | public BaseController(RoleManager roleManager, UserManager userManager, 18 | IConfiguration configuration) 19 | { 20 | RoleManager = roleManager; 21 | UserManager = userManager; 22 | Configuration = configuration; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LinkitAir/Controllers/FlightController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | using Core.Entities; 5 | using Core.Interfaces; 6 | using Mapster; 7 | using LinkitAir.ViewModels; 8 | using LinkitAir.ViewModelHelpers; 9 | 10 | namespace LinkitAir.Controllers 11 | { 12 | [Route("api/[controller]")] 13 | public class FlightController : Controller 14 | { 15 | private readonly IFlightService _flightService; 16 | 17 | public FlightController(IFlightService flightService) 18 | { 19 | _flightService = flightService; 20 | } 21 | 22 | /// 23 | /// Returns the flight for the given origin, destination and flight date combination 24 | /// 25 | /// 26 | /// Gets the flight from the chosen Origin Airport to the chosen Destination Airport on the chosen date 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// OK 32 | [HttpGet("GetFlightWithDate/origin/{originAirportId}/destination/{destinationAirportId}/flightdate/{flightDate}")] 33 | public IActionResult GetFlightForOriginAndDestinationAndDate(int originAirportId, int destinationAirportId, string flightDate) 34 | { 35 | var flightInstance = _flightService.GetFlightForOriginAndDestinationAndDate(originAirportId, destinationAirportId, flightDate); 36 | var viewModelHelper = new FlightViewModelAdapterHelper(); 37 | 38 | return new JsonResult(viewModelHelper.customAdapt(flightInstance)); 39 | } 40 | 41 | /// 42 | /// Returns all the flights for the given origin, destination combination 43 | /// 44 | /// 45 | /// Lists all the flights from the chosen Origin Airport to the chosen Destination Airport on the chosen date 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// OK 51 | [HttpGet("GetFlights/origin/{originAirportId}/destination/{destinationAirportId}/return/{returnTicket}")] 52 | public IActionResult GetFlightsForOriginAndDestination(int originAirportId, int destinationAirportId, bool returnTicket) 53 | { 54 | var flightInstances = _flightService.GetFlightsForOriginAndDestination(originAirportId, destinationAirportId, returnTicket); 55 | var viewModelHelper = new FlightViewModelAdapterHelper(); 56 | return new JsonResult(viewModelHelper.customAdapt(flightInstances)); 57 | } 58 | 59 | /// 60 | /// Returns all the alternative flights for the selected combination 61 | /// 62 | /// 63 | /// Lists all the alternative flights from/to other airports in the same cities(if the city has multiple airports) 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// 69 | /// OK 70 | [HttpGet("GetAlternativeFlights/origin/{originAirportId}/destination/{destinationAirportId}/without/{originalResultFlightInstanceId}/return/{returnTicket}")] 71 | public IActionResult GetAlternativeFlightsFromAndToSelectedCities(int originAirportId, int destinationAirportId, int originalResultFlightInstanceId, bool returnTicket) 72 | { 73 | var flights = _flightService.GetAlternativeFlightsFromAndToSelectedCities(originAirportId, destinationAirportId, originalResultFlightInstanceId, returnTicket); 74 | var viewModelHelper = new FlightViewModelAdapterHelper(); 75 | return new JsonResult(viewModelHelper.customAdapt(flights)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LinkitAir/Controllers/TokenController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Reflection.Metadata; 7 | using LinkitAir.ViewModels; 8 | using Microsoft.AspNetCore.Identity; 9 | using System.IdentityModel.Tokens.Jwt; 10 | using System.Security.Claims; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.IdentityModel.Tokens; 13 | using System.Text; 14 | using Core.Entities; 15 | 16 | namespace LinkitAir.Controllers 17 | { 18 | [Route("api/[controller]")] 19 | public class TokenController : BaseController 20 | { 21 | public TokenController(RoleManager roleManager, UserManager userManager, 22 | IConfiguration configuration) : base(roleManager, userManager, configuration) 23 | { 24 | } 25 | 26 | /// 27 | /// Authenticates the user 28 | /// 29 | /// TokenRequestViewModel 30 | /// OK 31 | /// If the client payload is invalid 32 | [HttpPost("[action]")] 33 | public async Task Jwt([FromBody]TokenRequestViewModel model) 34 | { 35 | if (model == null) return new StatusCodeResult(500); 36 | switch (model.grant_type) 37 | { 38 | case "password": 39 | return await GetToken(model); 40 | default: 41 | return new UnauthorizedResult(); 42 | } 43 | } 44 | 45 | private async Task GetToken(TokenRequestViewModel model) 46 | { 47 | try 48 | { 49 | var user = await UserManager.FindByNameAsync(model.username); 50 | if (user == null && model.username.Contains("@")) 51 | user = await UserManager.FindByEmailAsync(model.username); 52 | if (user == null || !await UserManager.CheckPasswordAsync(user, model.password)) 53 | { 54 | return new UnauthorizedResult(); 55 | } 56 | 57 | DateTime now = DateTime.UtcNow; 58 | // add the registered claims for JWT (RFC7519). 59 | // For more info, see https://tools.ietf.org/html/rfc7519#section-4.1 60 | var claims = new[] { 61 | new Claim(JwtRegisteredClaimNames.Sub, user.Id), 62 | new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), 63 | new Claim(JwtRegisteredClaimNames.Iat, 64 | new DateTimeOffset(now).ToUnixTimeSeconds().ToString()) 65 | // TODO: add additional claims here 66 | }; 67 | 68 | var tokenExpirationMins = 69 | Configuration.GetValue 70 | ("Auth:Jwt:TokenExpirationInMinutes"); 71 | var issuerSigningKey = new SymmetricSecurityKey( 72 | Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])); 73 | var token = new JwtSecurityToken( 74 | issuer: Configuration["Auth:Jwt:Issuer"], 75 | audience: Configuration["Auth:Jwt:Audience"], 76 | claims: claims, 77 | notBefore: now, 78 | expires: 79 | now.Add(TimeSpan.FromMinutes(tokenExpirationMins)), 80 | signingCredentials: new SigningCredentials( 81 | issuerSigningKey, 82 | SecurityAlgorithms.HmacSha256) 83 | ); 84 | 85 | var encodedToken = new 86 | JwtSecurityTokenHandler().WriteToken(token); 87 | var response = new TokenResponseViewModel() 88 | { 89 | token = encodedToken, 90 | expiration = tokenExpirationMins 91 | }; 92 | return Json(response); 93 | } 94 | catch (Exception ex) 95 | { 96 | return new UnauthorizedResult(); 97 | } 98 | } 99 | 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /LinkitAir/CustomMiddleware/RequestResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Internal; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using Core.Interfaces; 11 | 12 | namespace LinkitAir.CustomMiddleware 13 | { 14 | public class RequestResponseLoggingMiddleware 15 | { 16 | private readonly RequestDelegate _next; 17 | private readonly ILogger _logger; 18 | private IRequestLogService _requestLogService; 19 | 20 | 21 | public RequestResponseLoggingMiddleware(RequestDelegate next, 22 | ILoggerFactory loggerFactory) 23 | { 24 | 25 | _next = next; 26 | // loggerFactory.AddDebug(); 27 | _logger = loggerFactory 28 | .CreateLogger(); 29 | } 30 | 31 | public async Task Invoke(HttpContext context, IRequestLogService requestLogService) 32 | { 33 | _requestLogService = requestLogService; 34 | 35 | _logger.LogInformation(await FormatRequest(context.Request)); 36 | 37 | var originalBodyStream = context.Response.Body; 38 | 39 | using (var responseBody = new MemoryStream()) 40 | { 41 | context.Response.Body = responseBody; 42 | 43 | await _next(context); 44 | 45 | _logger.LogInformation(await FormatResponse(context.Response)); 46 | 47 | _requestLogService.CreateRequestLog(context.Request.Method, context.Response.StatusCode.ToString(), 48 | context.Request.Path, 27214); 49 | 50 | await responseBody.CopyToAsync(originalBodyStream); 51 | 52 | 53 | 54 | } 55 | } 56 | 57 | private async Task FormatRequest(HttpRequest request) 58 | { 59 | var body = request.Body; 60 | request.EnableRewind(); 61 | 62 | var buffer = new byte[Convert.ToInt32(request.ContentLength)]; 63 | await request.Body.ReadAsync(buffer, 0, buffer.Length); 64 | var bodyAsText = Encoding.UTF8.GetString(buffer); 65 | request.Body = body; 66 | 67 | return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}"; 68 | } 69 | 70 | private async Task FormatResponse(HttpResponse response) 71 | { 72 | response.Body.Seek(0, SeekOrigin.Begin); 73 | var text = await new StreamReader(response.Body).ReadToEndAsync(); 74 | response.Body.Seek(0, SeekOrigin.Begin); 75 | 76 | return $"Response {text}"; 77 | } 78 | 79 | //private async 80 | 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /LinkitAir/CustomMiddleware/RequestResponseLoggingMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace LinkitAir.CustomMiddleware 8 | { 9 | public static class RequestResponseLoggingMiddlewareExtensions 10 | { 11 | public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) 12 | { 13 | return builder.UseMiddleware(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LinkitAir/Helpers/HttpRequestResponseHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading.Tasks; 3 | using Core.Interfaces; 4 | using LinkitAir.ViewModels; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace LinkitAir.Helpers 8 | { 9 | public class HttpRequestResponseHelper 10 | { 11 | private readonly IRequestLogService _requestLogService; 12 | 13 | public HttpRequestResponseHelper(IRequestLogService requestLogService) 14 | { 15 | _requestLogService = requestLogService; 16 | } 17 | 18 | public async Task saveRequestResponseDetails(HttpContext context, Stopwatch sw) 19 | { 20 | await _requestLogService.CreateRequestLog(context.Request.Method, context.Response.StatusCode.ToString(), 21 | context.Request.Path, sw.Elapsed.Ticks); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LinkitAir/LinkitAir.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | Latest 7 | false 8 | ClientApp\ 9 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 10 | 11 | 12 | false 13 | 14 | 15 | 16 | api.xml 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | %(DistFiles.Identity) 91 | PreserveNewest 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /LinkitAir/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace LinkitAir 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LinkitAir/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.SpaServices.AngularCli; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.EntityFrameworkCore; 8 | using Infrastructure.Data; 9 | using Core.Interfaces; 10 | using Core.Services; 11 | using Core.Entities; 12 | using Swashbuckle.AspNetCore.Swagger; 13 | using System.IO; 14 | using System; 15 | using LinkitAir.Helpers; 16 | using System.Diagnostics; 17 | using Microsoft.AspNetCore.Identity; 18 | using Microsoft.AspNetCore.Authentication.JwtBearer; 19 | using Microsoft.IdentityModel.Tokens; 20 | using System.Text; 21 | 22 | namespace LinkitAir 23 | { 24 | public class Startup 25 | { 26 | public Startup(IConfiguration configuration) 27 | { 28 | Configuration = configuration; 29 | } 30 | 31 | public IConfiguration Configuration { get; } 32 | 33 | // This method gets called by the runtime. Use this method to add services to the container. 34 | public void ConfigureServices(IServiceCollection services) 35 | { 36 | /** 37 | * @TODO: Think about adding a third-party IoC Container for registering dependencies 38 | * in order to utilize the support for registry classes to avoid referencing the Infrastructure 39 | * project from the UI layer(this feature is not offered by the default IoC container provided by ASP.NET Core 40 | * */ 41 | 42 | services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); 43 | services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>)); 44 | services.AddScoped(); 45 | services.AddScoped(); 46 | services.AddScoped(); 47 | services.AddScoped(); 48 | services.AddScoped(); 49 | 50 | services.AddCors(); 51 | 52 | // services.AddScoped(); 53 | 54 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 55 | 56 | services.AddEntityFrameworkSqlServer(); 57 | 58 | /** 59 | * @TODO: This also creates a dependency, makes the UI layer dependent on Infrastructure. 60 | * Think about a way to handle this and possibly remove this dependency 61 | * */ 62 | services.AddDbContext(options => 63 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection") 64 | ) 65 | ); 66 | 67 | services.AddIdentity( 68 | opts => 69 | { 70 | opts.Password.RequireDigit = true; 71 | opts.Password.RequireLowercase = true; 72 | opts.Password.RequireUppercase = true; 73 | opts.Password.RequireNonAlphanumeric = false; 74 | opts.Password.RequiredLength = 7; 75 | }) 76 | .AddEntityFrameworkStores(); 77 | 78 | services.AddAuthentication(opts => 79 | { 80 | opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; 81 | opts.DefaultAuthenticateScheme = 82 | JwtBearerDefaults.AuthenticationScheme; 83 | opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 84 | }).AddJwtBearer(cfg => 85 | { 86 | cfg.RequireHttpsMetadata = false; 87 | cfg.SaveToken = true; 88 | cfg.TokenValidationParameters = new TokenValidationParameters() 89 | { 90 | // standard configuration 91 | ValidIssuer = Configuration["Auth:Jwt:Issuer"], 92 | ValidAudience = Configuration["Auth:Jwt:Audience"], 93 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])), 94 | ClockSkew = TimeSpan.Zero, 95 | // security switches 96 | RequireExpirationTime = true, 97 | ValidateIssuer = true, 98 | ValidateIssuerSigningKey = true, 99 | ValidateAudience = true 100 | }; 101 | }); 102 | 103 | 104 | // In production, the Angular files will be served from this directory 105 | services.AddSpaStaticFiles(configuration => 106 | { 107 | configuration.RootPath = "ClientApp/dist"; 108 | }); 109 | 110 | services.AddSwaggerGen(c => 111 | { 112 | c.SwaggerDoc("v1", new Info { 113 | Version = "v1", 114 | Title = "LinkitAir API", 115 | Description = "Web API for LinkitAir, airline services", 116 | Contact = new Contact 117 | { 118 | Name = "Robert Gliguroski", 119 | Email = "robert.gliguroski@gmail.com", 120 | Url = "https://twitter.com/gliguroskir" 121 | }, 122 | }); 123 | var filePath = Path.Combine(AppContext.BaseDirectory, "api.xml"); 124 | c.IncludeXmlComments(filePath); 125 | }); 126 | } 127 | 128 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 129 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 130 | { 131 | if (env.IsDevelopment()) 132 | { 133 | app.UseDeveloperExceptionPage(); 134 | } 135 | else 136 | { 137 | app.UseExceptionHandler("/Error"); 138 | app.UseHsts(); 139 | } 140 | 141 | app.UseCors(builder => 142 | builder.WithOrigins("http://localhost")); 143 | 144 | // app.UseRequestResponseLogging 145 | 146 | app.Use(async (context, next) => 147 | { 148 | var sw = new Stopwatch(); 149 | sw.Start(); 150 | await next.Invoke(); 151 | sw.Stop(); 152 | IRequestLogService service = (IRequestLogService)context.RequestServices.GetService(typeof(IRequestLogService)); 153 | var helper = new HttpRequestResponseHelper(service); 154 | await helper.saveRequestResponseDetails(context, sw); 155 | }); 156 | 157 | app.UseStaticFiles(); 158 | app.UseSpaStaticFiles(); 159 | 160 | app.UseSwagger(); 161 | app.UseSwaggerUI(c => 162 | { 163 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "LinkitAir API V1"); 164 | }); 165 | 166 | app.UseAuthentication(); 167 | 168 | app.UseMvc(routes => 169 | { 170 | routes.MapRoute( 171 | name: "default", 172 | template: "{controller}/{action=Index}/{id?}"); 173 | }); 174 | 175 | 176 | using (var serviceScope = app.ApplicationServices.GetRequiredService().CreateScope()) 177 | { 178 | var dbContext = serviceScope.ServiceProvider.GetService(); 179 | 180 | var roleManager = serviceScope.ServiceProvider.GetService>(); 181 | var userManager = serviceScope.ServiceProvider.GetService>(); 182 | 183 | // dbContext.Database.Migrate(); 184 | // DbSeeder.Seed(dbContext, roleManager, userManager); 185 | } 186 | 187 | app.UseSpa(spa => 188 | { 189 | // To learn more about options for serving an Angular SPA from ASP.NET Core, 190 | // see https://go.microsoft.com/fwlink/?linkid=864501 191 | 192 | spa.Options.SourcePath = "ClientApp"; 193 | 194 | if (env.IsDevelopment()) 195 | { 196 | //spa.UseAngularCliServer(npmScript: "start"); 197 | spa.UseProxyToSpaDevelopmentServer("http://localhost:4200"); 198 | } 199 | }); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /LinkitAir/ViewModelHelpers/FlightViewModelAdapterHelper.cs: -------------------------------------------------------------------------------- 1 | using Mapster; 2 | using Core.Entities; 3 | using LinkitAir.ViewModels; 4 | using System.Collections.Generic; 5 | 6 | namespace LinkitAir.ViewModelHelpers 7 | { 8 | public class FlightViewModelAdapterHelper 9 | { 10 | public List customAdapt(IEnumerable flightInstances) 11 | { 12 | List flightInstanceViewModels = new List(); 13 | var config = new TypeAdapterConfig(); 14 | foreach(var flightInstance in flightInstances) 15 | { 16 | config.NewConfig() 17 | .Map( 18 | dest => dest.OriginAirportName, src => src.FlightRoute.Origin.Name 19 | ).Map( 20 | dest => dest.DestinationAirportName, src => src.FlightRoute.Destination.Name 21 | ).Map( 22 | dest => dest.OriginCityName, src => src.FlightRoute.Origin.City.Name 23 | ).Map( 24 | dest => dest.DestinationCityName, src => src.FlightRoute.Destination.City.Name 25 | ).Map( 26 | dest => dest.FlightInstanceId, src => src.Id 27 | ).Map( 28 | dest => dest.FlightCode, src => src.Code 29 | ).Map( 30 | dest => dest.DepartureTime, src => src.DepartureTime.ToString("dddd, dd MMMM yyyy HH:mm") 31 | ).Map( 32 | dest => dest.ArrivalTime, src => src.ArrivalTime.ToString("dddd, dd MMMM yyyy HH:mm") 33 | ); 34 | IAdapter adapter = new Adapter(config); 35 | flightInstanceViewModels.Add(adapter.Adapt(flightInstance)); 36 | } 37 | 38 | return flightInstanceViewModels; 39 | } 40 | 41 | public FlightViewModel customAdapt(FlightInstance flightInstance) 42 | { 43 | var config = new TypeAdapterConfig(); 44 | config.NewConfig() 45 | .Map( 46 | dest => dest.OriginAirportName, src => src.FlightRoute.Origin.Name 47 | ).Map( 48 | dest => dest.DestinationAirportName, src => src.FlightRoute.Destination.Name 49 | ).Map( 50 | dest => dest.OriginCityName, src => src.FlightRoute.Origin.City.Name 51 | ).Map( 52 | dest => dest.DestinationCityName, src => src.FlightRoute.Destination.City.Name 53 | ).Map( 54 | dest => dest.FlightInstanceId, src => src.Id 55 | ).Map( 56 | dest => dest.FlightCode, src => src.Code 57 | ); 58 | IAdapter adapter = new Adapter(config); 59 | var flightInstanceViewModel = adapter.Adapt(flightInstance); 60 | return flightInstanceViewModel; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/AirportViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace LinkitAir.ViewModels 7 | { 8 | public class AirportViewModel 9 | { 10 | public int Id { get; set; } 11 | public string Code { get; set; } 12 | public string Name { get; set; } 13 | public string Description { get; set; } 14 | public int CityId { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/CityViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace LinkitAir.ViewModels 7 | { 8 | public class CityViewModel 9 | { 10 | public int Id { get; set; } 11 | public string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/CountryViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace LinkitAir.ViewModels 7 | { 8 | public class CountryViewModel 9 | { 10 | public int Id { get; set; } 11 | public string Name { get; set; } 12 | public string Code { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/FlightViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Entities; 3 | 4 | namespace LinkitAir.ViewModels 5 | { 6 | public class FlightViewModel 7 | { 8 | public int FlightInstanceId { get; set; } 9 | public string FlightCode { get; set; } 10 | public string OriginAirportName { get; set; } 11 | public string DestinationAirportName { get; set; } 12 | public string OriginCityName { get; set; } 13 | public string DestinationCityName { get; set; } 14 | public string DepartureTime { get; set; } 15 | public string ArrivalTime { get; set; } 16 | public int Price { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/TokenRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace LinkitAir.ViewModels 4 | { 5 | [JsonObject(MemberSerialization.OptOut)] 6 | public class TokenRequestViewModel 7 | { 8 | public string grant_type { get; set; } 9 | public string client_id { get; set; } 10 | public string client_secret { get; set; } 11 | public string username { get; set; } 12 | public string password { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LinkitAir/ViewModels/TokenResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace LinkitAir.ViewModels 4 | { 5 | [JsonObject(MemberSerialization.OptOut)] 6 | public class TokenResponseViewModel 7 | { 8 | public string token { get; set; } 9 | public int expiration { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LinkitAir/api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LinkitAir 5 | 6 | 7 | 8 | 9 | Counts all the processed requests 10 | 11 | 12 | Returns the total number of requests processed by the application 13 | 14 | 15 | 16 | 17 | Returns all the airport records 18 | 19 | 20 | Lists all the airport records in our database, without any filtering 21 | 22 | OK 23 | 24 | 25 | 26 | Returns all the destination airports for this origin 27 | 28 | 29 | Lists all the airports that the selected airports has flights to 30 | 31 | 32 | OK 33 | 34 | 35 | 36 | Returns the flight for the given origin, destination and flight date combination 37 | 38 | 39 | Gets the flight from the chosen Origin Airport to the chosen Destination Airport on the chosen date 40 | 41 | 42 | 43 | 44 | OK 45 | 46 | 47 | 48 | Returns all the flights for the given origin, destination combination 49 | 50 | 51 | Lists all the flights from the chosen Origin Airport to the chosen Destination Airport on the chosen date 52 | 53 | 54 | 55 | 56 | OK 57 | 58 | 59 | 60 | Returns all the alternative flights for the selected combination 61 | 62 | 63 | Lists all the alternative flights from/to other airports in the same cities(if the city has multiple airports) 64 | 65 | 66 | 67 | 68 | 69 | OK 70 | 71 | 72 | 73 | Authenticates the user 74 | 75 | TokenRequestViewModel 76 | OK 77 | If the client payload is invalid 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /LinkitAir/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LinkitAir/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=LinkitAir;User Id=LinkitAdmin;Password=SamplePassword123;Integrated Security=False;MultipleActiveResultSets=True" 4 | }, 5 | 6 | "Auth": { 7 | "Jwt": { 8 | "Issuer": "http://localhost:51736/", 9 | "Audience": "http://localhost:51736/", 10 | "Key": "---insert-your-own-key-here---", 11 | "TokenExpirationInMinutes": 720 12 | } 13 | }, 14 | 15 | "Logging": { 16 | "LogLevel": { 17 | "Default": "Warning" 18 | } 19 | }, 20 | "AllowedHosts": "*" 21 | } 22 | -------------------------------------------------------------------------------- /LinkitAir/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertgliguroski/clean-architecture-dot-net-core-angular/30cfb9930d7673032edef41961eab3ee4a4eafd1/LinkitAir/wwwroot/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinkitAir - Clean architecture with ASP.NET Core and Angular 2 | 3 | A modular, loosely-coupled web application, following the SOLID principles, built using ASP.NET Core 2.1 & Angular 5. It demonstrates the "Clean architecture". 4 | 5 | The motivation for choosing the Clean architecture is that I believe it is one of the best approaches to designing web applications today. It directly addresses and solves the main problem with the traditional "N-layer" architecture which was so dominant in the past years(most of the .NET projects I've witnessed have been built using this approach). The problem with the N-layer approach is that compile-time dependencies run vertically from the top layer to the bottom layer. That means that the UI layer directly depends on the BLL(Business Logic Layer), which in turn directly depends on the DAL(Data Access Layer). 6 | 7 | This means that the main application logic(which is contained in the BLL) will always depend on the existence of a database, which makes testing extremely hard. One approach often used to approach this problem is using Dependency Injection together with some Mocking functionality(they often go together with a Unit Testing Framework). 8 | 9 | This approach is perfectly fine for dealing with this issue, because using Dependency Injection actually inverts the responsibility of handling dependency - instead of having an object construct its needed dependencies itself, we are shifting that responsibility to another "object"(most often a DI framework) and we're providing those dependencies to the object at run time, instead of compile time. 10 | 11 | Clean architecture takes this approach one step further - instead of fixing a broken architecture with DI, why don't we have a completely new architecture which relies on the same principles DI relies on? 12 | 13 | So now, instead of having the Business logic layer depend on the Data access layer, let us have the dependency inverted and have the business logic in the center of the application(Core) and have the Infrastructure depend on it! 14 | 15 | This means that we will be creating a lot of Interfaces and put them in the Core(center of the application), together with the Entities and Domain Services(which will contain most of the business logic). 16 | 17 | This provides us with two major benefits: 18 | 19 | * The Core does not depend on Infrastructure, so we can easily write unit tests(and automated unit tests) for this layer(and test the bulk of the business logic) 20 | * the UI layer does not depend on Infrastructure, so it is very easy to swap implementations(e.g. in the Controllers) for testing purposes 21 | 22 | The following provides a simple layout of a web application organized by the principles defined in the Clean architecture: 23 | 24 | * Core project: Holds the Interfaces(for both Services and Repositories), Entities and the actual Services(which hold the business logic but rely on Interfaces and do not depend on types defined in Infrastructure), Specifications, Exceptions. Additional services which require infrastructure-related classes should also have their Interfaces here, but they will be implemented in the Infrastructure layer 25 | * Infrastructure project: Contains implementations related to data access, such as: Entity Framework DbContext, Data Migrations and data access code, most often classes following the Repository pattern(i.e. Repositories). Interfaces for services that require classes related to Infrastructure(files, logging etc.) should be implemented here, by the appropriate class implementations 26 | * UI project - Contains the Controllers, Views, ViewModels etc., all of which must not interact with Infrastructure directly, but strictly through interfaces defined in the Core layer. In practice this means that we will not have any instantiation of types defined in Infrastructure 27 | 28 | ## Business logic 29 | 30 | This is a sample web application for a fictitious airline. It provides the users with a way to search for flights from the desired origin airport to the desired destination airport. It will list all the available flights with their price. 31 | 32 | It will also offer alternative flights from other airports in the same city where the origin/destination airports are located. 33 | 34 | The application logs each request made to the server in a custom middleware and offers the following basic statistics for the admin users: 35 | 36 | * Total number of requests processed 37 | * Total number of requests resulting in an OK, 4xx and 5xx responses 38 | * Number of requests grouped by exact response status code(e.g. 200, 201, 401, 404, 500 etc.) 39 | * Average response time of all requests 40 | * Minimum response time of all requests 41 | * Maximum response time of all requests 42 | 43 | ## Technology stack 44 | 45 | * ASP.NET Core 2.1 46 | * Angular 5 47 | * Angular Material (v. 5.2.4) 48 | * Entity Framework Core (Code First) 49 | * ASP.NET Core Identity 50 | * JSON Web Tokens (JWT) 51 | * Sql Server 2017 Express 52 | * Swagger (via Swashbuckle) 53 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@angular/cdk": { 6 | "version": "5.2.5", 7 | "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-5.2.5.tgz", 8 | "integrity": "sha512-GN8m1d+VcCE9+Bgwv06Y8YJKyZ0i9ZIq2ZPBcJYt+KVgnVVRg4JkyUNxud07LNsvzOX22DquHqmIZiC4hAG7Ag==", 9 | "requires": { 10 | "tslib": "1.9.3" 11 | } 12 | }, 13 | "@angular/forms": { 14 | "version": "4.4.7", 15 | "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-4.4.7.tgz", 16 | "integrity": "sha512-EXGutI4GNBptpwkCQdCTxWAlJll8aCV7m3cA1FHZgFP7VNSgYF0pD+PscM5jSeajG30cRjaKxgL4cqj6yMMtww==", 17 | "requires": { 18 | "tslib": "1.9.3" 19 | } 20 | }, 21 | "@angular/material": { 22 | "version": "5.2.5", 23 | "resolved": "https://registry.npmjs.org/@angular/material/-/material-5.2.5.tgz", 24 | "integrity": "sha512-IltfBeTJWnmZehOQNQ7KoFs7MGWuZTe0g21hIitGkusVNt1cIoTD24xKH5jwztjH19c04IgiwonpurMKM6pBCQ==", 25 | "requires": { 26 | "tslib": "1.9.3" 27 | } 28 | }, 29 | "parse5": { 30 | "version": "5.0.0", 31 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.0.0.tgz", 32 | "integrity": "sha512-0ywuiUOnpWWeil5grH2rxjyTJoeQVwyBuO2si6QIU9dWtj2npjuyK1HaY1RbLnVfDhEbhyAPNUBKRK0Xj2xE0w==" 33 | }, 34 | "tslib": { 35 | "version": "1.9.3", 36 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 37 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" 38 | } 39 | } 40 | } 41 | --------------------------------------------------------------------------------