├── .DS_Store ├── .angular-cli.json ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── ClientApp ├── .DS_Store ├── app │ ├── app.config.ts │ ├── app.error-handler.ts │ ├── app.module.ts │ ├── components │ │ ├── admin │ │ │ └── admin.component.ts │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ └── app.component.ts │ │ ├── counter │ │ │ ├── counter.component.html │ │ │ └── counter.component.ts │ │ ├── fetchdata │ │ │ ├── fetchdata.component.html │ │ │ └── fetchdata.component.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ │ ├── navmenu │ │ │ ├── navmenu.component.css │ │ │ ├── navmenu.component.html │ │ │ └── navmenu.component.ts │ │ ├── shared │ │ │ └── pagination.component.ts │ │ ├── vehicle-form │ │ │ ├── vehicle-form.component.css │ │ │ ├── vehicle-form.component.html │ │ │ └── vehicle-form.component.ts │ │ ├── vehicle-list │ │ │ ├── vehicle-list.html │ │ │ └── vehicle-list.ts │ │ └── view-vehicle │ │ │ ├── view-vehicle.html │ │ │ └── view-vehicle.ts │ ├── models │ │ └── vehicle.ts │ └── services │ │ ├── admin-auth-guard.service.ts │ │ ├── auth-gaurd.service.ts │ │ ├── auth.service.ts │ │ ├── photo.service.ts │ │ ├── progress.service.ts │ │ └── vehicle.service.ts ├── boot-client.ts └── boot-server.ts ├── Controllers ├── .DS_Store ├── AppPolicies.cs ├── FeaturesController.cs ├── HomeController.cs ├── MakesController.cs ├── PhotosController.cs ├── Resources │ ├── ContactResource.cs │ ├── KeyValuePairResource.cs │ ├── MakeResource.cs │ ├── PhotoResource.cs │ ├── QueryResultResource.cs │ ├── SaveVehicleResource.cs │ ├── VehicleQueryResource.cs │ └── VehicleResource.cs ├── SampleDataController.cs └── VehiclesController.cs ├── Core ├── FileSystemPhotoStorage.cs ├── IPhotoRepository.cs ├── IPhotoService.cs ├── IPhotoStorage.cs ├── IUnitOfWork.cs ├── IVehicleRepository.cs ├── Models │ ├── Feature.cs │ ├── Make.cs │ ├── Model.cs │ ├── Photo.cs │ ├── PhotoSettings.cs │ ├── QueryResult.cs │ ├── Vehicle.cs │ ├── VehicleFeature.cs │ └── VehicleQuery.cs └── PhotoService.cs ├── Extensions ├── IQueryObject.cs └── IQueryableExtensions.cs ├── Mapping └── MappingProfile.cs ├── Migrations ├── 20170320232928_InitialModel.Designer.cs ├── 20170320232928_InitialModel.cs ├── 20170321005120_ApplyConstraints.Designer.cs ├── 20170321005120_ApplyConstraints.cs ├── 20170321005742_SeedDatabase.Designer.cs ├── 20170321005742_SeedDatabase.cs ├── 20170322022552_AddFeature.Designer.cs ├── 20170322022552_AddFeature.cs ├── 20170322022625_SeedFeatures.Designer.cs ├── 20170322022625_SeedFeatures.cs ├── 20170326223931_AddVehicle.Designer.cs ├── 20170326223931_AddVehicle.cs ├── 20170412005215_AddPhoto.Designer.cs ├── 20170412005215_AddPhoto.cs └── VegaDbContextModelSnapshot.cs ├── Persistence ├── PhotoRepository.cs ├── UnitOfWork.cs ├── VegaDbContext.cs └── VehicleRepository.cs ├── Program.cs ├── README.md ├── Startup.cs ├── Vega.csproj ├── Views ├── Home │ └── Index.cshtml ├── Shared │ ├── Error.cshtml │ └── _Layout.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.json ├── global.json ├── package.json ├── tsconfig.json ├── web.config ├── webpack.config.js ├── webpack.config.vendor.js └── wwwroot ├── .DS_Store └── favicon.ico /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/.DS_Store -------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "hello-world" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "ClientApp", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "styles.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 | }, 41 | { 42 | "project": "src/tsconfig.spec.json" 43 | }, 44 | { 45 | "project": "e2e/tsconfig.e2e.json" 46 | } 47 | ], 48 | "test": { 49 | "karma": { 50 | "config": "./karma.conf.js" 51 | } 52 | }, 53 | "defaults": { 54 | "styleExt": "css", 55 | "component": {} 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.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 | /wwwroot/uploads/** 32 | /wwwroot/dist/** 33 | /ClientApp/dist/** 34 | 35 | # Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 36 | !/wwwroot/dist/_placeholder.txt 37 | !/ClientApp/dist/_placeholder.txt 38 | 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | # DNX 54 | project.lock.json 55 | artifacts/ 56 | 57 | *_i.c 58 | *_p.c 59 | *_i.h 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.svclog 80 | *.scc 81 | 82 | # Chutzpah Test files 83 | _Chutzpah* 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opendb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Microsoft Azure ApplicationInsights config file 174 | ApplicationInsights.config 175 | 176 | # Windows Store app package directory 177 | AppPackages/ 178 | BundleArtifacts/ 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 | *.pfx 193 | *.publishsettings 194 | orleans.codegen.cs 195 | 196 | # Workaround for https://github.com/aspnet/JavaScriptServices/issues/235 197 | /node_modules/** 198 | !/node_modules/_placeholder.txt 199 | 200 | /yarn.lock 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 | 248 | # FAKE - F# Make 249 | .fake/ 250 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (web)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.1/Vega.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false, 13 | "internalConsoleOptions": "openOnSessionStart", 14 | "launchBrowser": { 15 | "enabled": true, 16 | "args": "${auto-detect-url}", 17 | "windows": { 18 | "command": "cmd.exe", 19 | "args": "/C start ${auto-detect-url}" 20 | }, 21 | "osx": { 22 | "command": "open" 23 | }, 24 | "linux": { 25 | "command": "xdg-open" 26 | } 27 | }, 28 | "env": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | }, 31 | "sourceFileMap": { 32 | "/Views": "${workspaceRoot}/Views" 33 | } 34 | }, 35 | { 36 | "name": ".NET Core Attach", 37 | "type": "coreclr", 38 | "request": "attach", 39 | "processId": "${command:pickProcess}" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}/Vega.csproj" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /ClientApp/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/ClientApp/.DS_Store -------------------------------------------------------------------------------- /ClientApp/app/app.config.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/ClientApp/app/app.config.ts -------------------------------------------------------------------------------- /ClientApp/app/app.error-handler.ts: -------------------------------------------------------------------------------- 1 | import * as Raven from 'raven-js'; 2 | import { ToastyService } from 'ng2-toasty'; 3 | import { ErrorHandler, Inject, NgZone, isDevMode } from '@angular/core'; 4 | 5 | export class AppErrorHandler implements ErrorHandler { 6 | constructor( 7 | private ngZone: NgZone, 8 | @Inject(ToastyService) private toastyService: ToastyService) { 9 | } 10 | 11 | handleError(error: any): void { 12 | this.ngZone.run(() => { 13 | this.toastyService.error({ 14 | title: 'Error', 15 | msg: 'An unexpected error happened.', 16 | theme: 'bootstrap', 17 | showClose: true, 18 | timeout: 5000 19 | }); 20 | }); 21 | 22 | if (!isDevMode()) 23 | Raven.captureException(error.originalError || error); 24 | else 25 | throw error; 26 | } 27 | } -------------------------------------------------------------------------------- /ClientApp/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { AdminAuthGuard } from './services/admin-auth-guard.service'; 2 | import { AuthGuard } from './services/auth-gaurd.service'; 3 | import { AdminComponent } from './components/admin/admin.component'; 4 | import { Auth } from './services/auth.service'; 5 | import { BrowserXhr } from '@angular/http'; 6 | import { BrowserXhrWithProgress, ProgressService } from './services/progress.service'; 7 | import { ViewVehicleComponent } from './components/view-vehicle/view-vehicle'; 8 | import { PaginationComponent } from './components/shared/pagination.component'; 9 | import { VehicleListComponent } from './components/vehicle-list/vehicle-list'; 10 | import * as Raven from 'raven-js'; 11 | import { FormsModule } from '@angular/forms'; 12 | import { NgModule, ErrorHandler } from '@angular/core'; 13 | import { RouterModule } from '@angular/router'; 14 | import { ToastyModule } from 'ng2-toasty'; 15 | import { UniversalModule } from 'angular2-universal'; 16 | import { ChartModule } from 'angular2-chartjs'; 17 | 18 | import { AppComponent } from './components/app/app.component' 19 | import { AppErrorHandler } from './app.error-handler'; 20 | import { VehicleService } from './services/vehicle.service'; 21 | import { NavMenuComponent } from './components/navmenu/navmenu.component'; 22 | import { HomeComponent } from './components/home/home.component'; 23 | import { FetchDataComponent } from './components/fetchdata/fetchdata.component'; 24 | import { CounterComponent } from './components/counter/counter.component'; 25 | import { VehicleFormComponent } from './components/vehicle-form/vehicle-form.component'; 26 | import { PhotoService } from "./services/photo.service"; 27 | import { AUTH_PROVIDERS } from "angular2-jwt/angular2-jwt"; 28 | 29 | Raven.config('https://d37bba0c459b46e0857e6e2b3aeff09b@sentry.io/155312').install(); 30 | 31 | @NgModule({ 32 | bootstrap: [ AppComponent ], 33 | declarations: [ 34 | AppComponent, 35 | NavMenuComponent, 36 | CounterComponent, 37 | FetchDataComponent, 38 | HomeComponent, 39 | VehicleFormComponent, 40 | VehicleListComponent, 41 | ViewVehicleComponent, 42 | PaginationComponent, 43 | AdminComponent 44 | ], 45 | imports: [ 46 | UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too. 47 | FormsModule, 48 | ToastyModule.forRoot(), 49 | ChartModule, 50 | RouterModule.forRoot([ 51 | { path: '', redirectTo: 'vehicles', pathMatch: 'full' }, 52 | { path: 'vehicles/new', component: VehicleFormComponent, canActivate: [ AuthGuard ] }, 53 | { path: 'vehicles/edit/:id', component: VehicleFormComponent, canActivate: [ AuthGuard ] }, 54 | { path: 'vehicles/:id', component: ViewVehicleComponent }, 55 | { path: 'vehicles', component: VehicleListComponent }, 56 | { path: 'admin', component: AdminComponent, canActivate: [ AdminAuthGuard ] }, 57 | { path: 'home', component: HomeComponent }, 58 | { path: '**', redirectTo: 'home' } 59 | ]) 60 | ], 61 | providers: [ 62 | { provide: ErrorHandler, useClass: AppErrorHandler }, 63 | Auth, 64 | AuthGuard, 65 | AUTH_PROVIDERS, 66 | AdminAuthGuard, 67 | VehicleService, 68 | PhotoService 69 | ] 70 | }) 71 | export class AppModule { 72 | } 73 | -------------------------------------------------------------------------------- /ClientApp/app/components/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | template: `

Admin

5 | 6 | ` 7 | }) 8 | 9 | export class AdminComponent implements OnInit { 10 | data = { 11 | labels: ['BMW', 'Audi', 'Mazda'], 12 | datasets: [ 13 | { 14 | data: [5, 3, 1], 15 | backgroundColor: [ 16 | "#ff6384", 17 | "#36a2eb", 18 | "#ffce56" 19 | ] 20 | } 21 | ] 22 | }; 23 | 24 | constructor() { } 25 | 26 | ngOnInit() { } 27 | } -------------------------------------------------------------------------------- /ClientApp/app/components/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 | -------------------------------------------------------------------------------- /ClientApp/app/components/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /ClientApp/app/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /ClientApp/app/components/counter/counter.component.html: -------------------------------------------------------------------------------- 1 |

Counter

2 | 3 |

This is a simple example of an Angular 2 component.

4 | 5 |

Current count: {{ currentCount }}

6 | 7 | 8 | -------------------------------------------------------------------------------- /ClientApp/app/components/counter/counter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'counter', 5 | templateUrl: './counter.component.html' 6 | }) 7 | export class CounterComponent { 8 | public currentCount = 0; 9 | 10 | public incrementCounter() { 11 | this.currentCount++; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ClientApp/app/components/fetchdata/fetchdata.component.html: -------------------------------------------------------------------------------- 1 |

Weather forecast 2

2 | 3 |

This component demonstrates fetching data from the server.

4 | 5 |

Loading...

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
DateTemp. (C)Temp. (F)Summary
{{ forecast.dateFormatted }}{{ forecast.temperatureC }}{{ forecast.temperatureF }}{{ forecast.summary }}
25 | -------------------------------------------------------------------------------- /ClientApp/app/components/fetchdata/fetchdata.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | 4 | @Component({ 5 | selector: 'fetchdata', 6 | templateUrl: './fetchdata.component.html' 7 | }) 8 | export class FetchDataComponent { 9 | public forecasts: WeatherForecast[]; 10 | 11 | constructor(http: Http) { 12 | http.get('/api/SampleData/WeatherForecasts').subscribe(result => { 13 | this.forecasts = result.json() as WeatherForecast[]; 14 | }); 15 | } 16 | } 17 | 18 | interface WeatherForecast { 19 | dateFormatted: string; 20 | temperatureC: number; 21 | temperatureF: number; 22 | summary: string; 23 | } 24 | -------------------------------------------------------------------------------- /ClientApp/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |

Hello, world!

2 |

Welcome to your new single-page application, built with:

3 | 9 |

To help you get started, we've also set up:

10 | 17 | -------------------------------------------------------------------------------- /ClientApp/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home', 5 | templateUrl: './home.component.html' 6 | }) 7 | export class HomeComponent { 8 | } 9 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.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 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.component.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.component.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from './../../services/auth.service'; 2 | import { Component } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'nav-menu', 6 | templateUrl: './navmenu.component.html', 7 | styleUrls: ['./navmenu.component.css'] 8 | }) 9 | export class NavMenuComponent { 10 | constructor(private auth: Auth) {} 11 | } 12 | -------------------------------------------------------------------------------- /ClientApp/app/components/shared/pagination.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | Output, 5 | EventEmitter } from '@angular/core'; 6 | import { OnChanges } from '@angular/core'; 7 | 8 | @Component({ 9 | selector: 'pagination', 10 | template: ` 11 | 28 | ` 29 | }) 30 | export class PaginationComponent implements OnChanges { 31 | @Input('total-items') totalItems; 32 | @Input('page-size') pageSize = 10; 33 | @Output('page-changed') pageChanged = new EventEmitter(); 34 | pages: any[]; 35 | currentPage = 1; 36 | 37 | ngOnChanges(){ 38 | this.currentPage = 1; 39 | 40 | var pagesCount = Math.ceil(this.totalItems / this.pageSize); 41 | this.pages = []; 42 | for (var i = 1; i <= pagesCount; i++) 43 | this.pages.push(i); 44 | } 45 | 46 | changePage(page){ 47 | this.currentPage = page; 48 | this.pageChanged.emit(page); 49 | } 50 | 51 | previous(){ 52 | if (this.currentPage == 1) 53 | return; 54 | 55 | this.currentPage--; 56 | this.pageChanged.emit(this.currentPage); 57 | } 58 | 59 | next(){ 60 | if (this.currentPage == this.pages.length) 61 | return; 62 | 63 | this.currentPage++; 64 | this.pageChanged.emit(this.currentPage); 65 | } 66 | } -------------------------------------------------------------------------------- /ClientApp/app/components/vehicle-form/vehicle-form.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/ClientApp/app/components/vehicle-form/vehicle-form.component.css -------------------------------------------------------------------------------- /ClientApp/app/components/vehicle-form/vehicle-form.component.html: -------------------------------------------------------------------------------- 1 |

New Vehicle

2 |

3 | {{ vehicle | json }} 4 |

5 |
6 |
7 | 8 | 12 |
Please specify the make.
13 |
14 |
15 | 16 | 20 |
Please specify the model.
21 |
22 |

Is this vehicle registered?

23 | 26 | 29 |

Features

30 |
31 | 34 |
35 |

Contact

36 |
37 | 38 | 39 |
Please specify the contact name.
40 |
41 |
42 | 43 | 44 |
Please specify the contact phone.
45 |
46 |
47 | 48 | 49 |
50 | 51 |
-------------------------------------------------------------------------------- /ClientApp/app/components/vehicle-form/vehicle-form.component.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'underscore'; 2 | import { SaveVehicle, Vehicle } from './../../models/vehicle'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { ActivatedRoute, Router } from '@angular/router'; 5 | import { VehicleService } from './../../services/vehicle.service'; 6 | import { Component, OnInit } from '@angular/core'; 7 | import { ToastyService } from "ng2-toasty"; 8 | import 'rxjs/add/Observable/forkJoin'; 9 | 10 | @Component({ 11 | selector: 'app-vehicle-form', 12 | templateUrl: './vehicle-form.component.html', 13 | styleUrls: ['./vehicle-form.component.css'] 14 | }) 15 | export class VehicleFormComponent implements OnInit { 16 | makes: any[]; 17 | models: any[]; 18 | features: any[]; 19 | vehicle: SaveVehicle = { 20 | id: 0, 21 | makeId: 0, 22 | modelId: 0, 23 | isRegistered: false, 24 | features: [], 25 | contact: { 26 | name: '', 27 | email: '', 28 | phone: '', 29 | } 30 | }; 31 | 32 | constructor( 33 | private route: ActivatedRoute, 34 | private router: Router, 35 | private vehicleService: VehicleService, 36 | private toastyService: ToastyService) { 37 | 38 | route.params.subscribe(p => { 39 | this.vehicle.id = +p['id'] || 0; 40 | }); 41 | } 42 | 43 | ngOnInit() { 44 | var sources = [ 45 | this.vehicleService.getMakes(), 46 | this.vehicleService.getFeatures(), 47 | ]; 48 | 49 | if (this.vehicle.id) 50 | sources.push(this.vehicleService.getVehicle(this.vehicle.id)); 51 | 52 | Observable.forkJoin(sources).subscribe(data => { 53 | this.makes = data[0]; 54 | this.features = data[1]; 55 | 56 | if (this.vehicle.id) { 57 | this.setVehicle(data[2]); 58 | this.populateModels(); 59 | } 60 | }, err => { 61 | if (err.status == 404) 62 | this.router.navigate(['/home']); 63 | }); 64 | } 65 | 66 | private setVehicle(v: Vehicle) { 67 | this.vehicle.id = v.id; 68 | this.vehicle.makeId = v.make.id; 69 | this.vehicle.modelId = v.model.id; 70 | this.vehicle.isRegistered = v.isRegistered; 71 | this.vehicle.contact = v.contact; 72 | this.vehicle.features = _.pluck(v.features, 'id'); 73 | } 74 | 75 | onMakeChange() { 76 | this.populateModels(); 77 | 78 | delete this.vehicle.modelId; 79 | } 80 | 81 | private populateModels() { 82 | var selectedMake = this.makes.find(m => m.id == this.vehicle.makeId); 83 | this.models = selectedMake ? selectedMake.models : []; 84 | } 85 | 86 | onFeatureToggle(featureId, $event) { 87 | if ($event.target.checked) 88 | this.vehicle.features.push(featureId); 89 | else { 90 | var index = this.vehicle.features.indexOf(featureId); 91 | this.vehicle.features.splice(index, 1); 92 | } 93 | } 94 | 95 | submit() { 96 | var result$ = (this.vehicle.id) ? this.vehicleService.update(this.vehicle) : this.vehicleService.create(this.vehicle); 97 | result$.subscribe(vehicle => { 98 | this.toastyService.success({ 99 | title: 'Success', 100 | msg: 'Data was sucessfully saved.', 101 | theme: 'bootstrap', 102 | showClose: true, 103 | timeout: 5000 104 | }); 105 | this.router.navigate(['/vehicles/', vehicle.id]) 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ClientApp/app/components/vehicle-list/vehicle-list.html: -------------------------------------------------------------------------------- 1 |

Vehicles

2 |

3 | New Vehicle 4 |

5 |
6 |
7 | 8 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 |
19 |
20 | {{ c.title }} 21 | 26 |
27 |
28 | {{ c.title }} 29 |
30 |
{{ v.id }}{{ v.make.name }}{{ v.model.name }}{{ v.contact.name }} 40 | View 41 |
45 | -------------------------------------------------------------------------------- /ClientApp/app/components/vehicle-list/vehicle-list.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from './../../services/auth.service'; 2 | import { Vehicle, KeyValuePair } from './../../models/vehicle'; 3 | import { VehicleService } from './../../services/vehicle.service'; 4 | import { Component, OnInit } from '@angular/core'; 5 | 6 | @Component({ 7 | templateUrl: 'vehicle-list.html' 8 | }) 9 | export class VehicleListComponent implements OnInit { 10 | private readonly PAGE_SIZE = 3; 11 | 12 | queryResult: any = {}; 13 | makes: KeyValuePair[]; 14 | query: any = { 15 | pageSize: this.PAGE_SIZE 16 | }; 17 | columns = [ 18 | { title: 'Id' }, 19 | { title: 'Contact Name', key: 'contactName', isSortable: true }, 20 | { title: 'Make', key: 'make', isSortable: true }, 21 | { title: 'Model', key: 'model', isSortable: true }, 22 | { } 23 | ]; 24 | 25 | constructor(private vehicleService: VehicleService, private auth: Auth) { } 26 | 27 | ngOnInit() { 28 | this.vehicleService.getMakes() 29 | .subscribe(makes => this.makes = makes); 30 | 31 | this.populateVehicles(); 32 | } 33 | 34 | private populateVehicles() { 35 | this.vehicleService.getVehicles(this.query) 36 | .subscribe(result => this.queryResult = result); 37 | } 38 | 39 | onFilterChange() { 40 | this.query.page = 1; 41 | this.populateVehicles(); 42 | } 43 | 44 | resetFilter() { 45 | this.query = { 46 | page: 1, 47 | pageSize: this.PAGE_SIZE 48 | }; 49 | this.populateVehicles(); 50 | } 51 | 52 | sortBy(columnName) { 53 | if (this.query.sortBy === columnName) { 54 | this.query.isSortAscending = !this.query.isSortAscending; 55 | } else { 56 | this.query.sortBy = columnName; 57 | this.query.isSortAscending = true; 58 | } 59 | this.populateVehicles(); 60 | } 61 | 62 | onPageChange(page) { 63 | this.query.page = page; 64 | this.populateVehicles(); 65 | } 66 | } -------------------------------------------------------------------------------- /ClientApp/app/components/view-vehicle/view-vehicle.html: -------------------------------------------------------------------------------- 1 |

Vehicle

2 |
3 | 4 | 5 | 9 | 10 | 11 |
12 | 13 |
14 |

Basics

15 |
    16 |
  • Make: {{ vehicle.make.name }}
  • 17 |
  • Model: {{ vehicle.model.name }}
  • 18 |
  • Registered: {{ vehicle.isRegistered ? 'Yes' : 'No' }} 19 |
20 |

Features

21 |
    22 |
  • {{ f.name }}
  • 23 |
24 |

Contact

25 |
    26 |
  • Contact Name: {{ vehicle.contact.name }}
  • 27 |
  • Contact Phone: {{ vehicle.contact.phone }}
  • 28 |
  • Contact Email: {{ vehicle.contact.email }}
  • 29 |
30 |
31 |

32 | Edit 33 | 34 | View All Vehicles 35 |

36 |
37 | 38 |
39 |

Photos

40 | 41 |
42 |
43 | {{ progress.percentage }}% Complete 44 |
45 |
46 | 47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /ClientApp/app/components/view-vehicle/view-vehicle.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from './../../services/auth.service'; 2 | import { BrowserXhr } from '@angular/http'; 3 | import { ProgressService, BrowserXhrWithProgress } from './../../services/progress.service'; 4 | import { PhotoService } from './../../services/photo.service'; 5 | import { ToastyService } from 'ng2-toasty'; 6 | import { VehicleService } from './../../services/vehicle.service'; 7 | import { Component, OnInit, ElementRef, ViewChild, NgZone } from '@angular/core'; 8 | import { ActivatedRoute, Router } from '@angular/router'; 9 | 10 | @Component({ 11 | templateUrl: 'view-vehicle.html', 12 | providers: [ 13 | { provide: BrowserXhr, useClass: BrowserXhrWithProgress }, 14 | ProgressService 15 | ] 16 | }) 17 | export class ViewVehicleComponent implements OnInit { 18 | @ViewChild('fileInput') fileInput: ElementRef; 19 | vehicle: any; 20 | vehicleId: number; 21 | photos: any[]; 22 | progress: any; 23 | 24 | constructor( 25 | private auth: Auth, 26 | private zone: NgZone, 27 | private route: ActivatedRoute, 28 | private router: Router, 29 | private toasty: ToastyService, 30 | private progressService: ProgressService, 31 | private photoService: PhotoService, 32 | private vehicleService: VehicleService) { 33 | 34 | route.params.subscribe(p => { 35 | this.vehicleId = +p['id']; 36 | if (isNaN(this.vehicleId) || this.vehicleId <= 0) { 37 | router.navigate(['/vehicles']); 38 | return; 39 | } 40 | }); 41 | } 42 | 43 | ngOnInit() { 44 | this.photoService.getPhotos(this.vehicleId) 45 | .subscribe(photos => this.photos = photos); 46 | 47 | this.vehicleService.getVehicle(this.vehicleId) 48 | .subscribe( 49 | v => this.vehicle = v, 50 | err => { 51 | if (err.status == 404) { 52 | this.router.navigate(['/vehicles']); 53 | return; 54 | } 55 | }); 56 | } 57 | 58 | delete() { 59 | if (confirm("Are you sure?")) { 60 | this.vehicleService.delete(this.vehicle.id) 61 | .subscribe(x => { 62 | this.router.navigate(['/vehicles']); 63 | }); 64 | } 65 | } 66 | 67 | uploadPhoto() { 68 | this.progressService.startTracking() 69 | .subscribe(progress => { 70 | this.zone.run(() => { 71 | this.progress = progress; 72 | }); 73 | }, 74 | null, 75 | () => { this.progress = null; }); 76 | 77 | var nativeElement: HTMLInputElement = this.fileInput.nativeElement; 78 | var file = nativeElement.files[0]; 79 | nativeElement.value = ''; 80 | this.photoService.upload(this.vehicleId, file) 81 | .subscribe(photo => { 82 | this.photos.push(photo); 83 | }, 84 | err => { 85 | this.toasty.error({ 86 | title: 'Error', 87 | msg: err.text(), 88 | theme: 'bootstrap', 89 | showClose: true, 90 | timeout: 5000 91 | }); 92 | }); 93 | } 94 | } -------------------------------------------------------------------------------- /ClientApp/app/models/vehicle.ts: -------------------------------------------------------------------------------- 1 | import { Contact } from './vehicle'; 2 | 3 | export interface KeyValuePair { 4 | id: number; 5 | name: string; 6 | } 7 | 8 | export interface Contact { 9 | name: string; 10 | phone: string; 11 | email: string; 12 | } 13 | 14 | export interface Vehicle { 15 | id: number; 16 | model: KeyValuePair; 17 | make: KeyValuePair; 18 | isRegistered: boolean; 19 | features: KeyValuePair[]; 20 | contact: Contact; 21 | lastUpdate: string; 22 | } 23 | 24 | export interface SaveVehicle { 25 | id: number; 26 | modelId: number; 27 | makeId: number; 28 | isRegistered: boolean; 29 | features: number[]; 30 | contact: Contact; 31 | } -------------------------------------------------------------------------------- /ClientApp/app/services/admin-auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from './auth-gaurd.service'; 2 | import { CanActivate } from '@angular/router'; 3 | import { Auth } from './auth.service'; 4 | import { Injectable } from '@angular/core'; 5 | 6 | @Injectable() 7 | export class AdminAuthGuard extends AuthGuard { 8 | 9 | constructor(auth: Auth) { 10 | super(auth); 11 | } 12 | 13 | canActivate() { 14 | var isAuthenticated = super.canActivate(); 15 | 16 | return isAuthenticated ? this.auth.isInRole('Admin') : false; 17 | } 18 | } -------------------------------------------------------------------------------- /ClientApp/app/services/auth-gaurd.service.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate } from '@angular/router'; 2 | import { Auth } from './auth.service'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable() 6 | export class AuthGuard implements CanActivate { 7 | 8 | constructor(protected auth: Auth) { } 9 | 10 | canActivate() { 11 | if (this.auth.authenticated()) 12 | return true; 13 | 14 | window.location.href = 'https://vegaproject.auth0.com/login?client=RfRu3un13aOO73C7X2mH41qxfHRbUc33'; 15 | return false; 16 | } 17 | } -------------------------------------------------------------------------------- /ClientApp/app/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { JwtHelper } from 'angular2-jwt'; 2 | // app/auth.service.ts 3 | 4 | import { Injectable } from '@angular/core'; 5 | import { tokenNotExpired } from 'angular2-jwt'; 6 | 7 | // Avoid name not found warnings 8 | import Auth0Lock from 'auth0-lock'; 9 | 10 | @Injectable() 11 | export class Auth { 12 | profile: any; 13 | private roles: string[] = []; 14 | 15 | // Configure Auth0 16 | lock = new Auth0Lock('RfRu3un13aOO73C7X2mH41qxfHRbUc33', 'vegaproject.auth0.com', {}); 17 | 18 | constructor() { 19 | this.readUserFromLocalStorage(); 20 | 21 | this.lock.on("authenticated", (authResult) => this.onUserAuthenticated(authResult)); 22 | } 23 | 24 | private onUserAuthenticated(authResult) { 25 | localStorage.setItem('token', authResult.accessToken); 26 | 27 | this.lock.getUserInfo(authResult.accessToken, (error, profile) => { 28 | if (error) 29 | throw error; 30 | 31 | localStorage.setItem('profile', JSON.stringify(profile)); 32 | 33 | this.readUserFromLocalStorage(); 34 | }); 35 | } 36 | 37 | private readUserFromLocalStorage() { 38 | this.profile = JSON.parse(localStorage.getItem('profile')); 39 | 40 | var token = localStorage.getItem('token'); 41 | if (token) { 42 | var jwtHelper = new JwtHelper(); 43 | var decodedToken = jwtHelper.decodeToken(token); 44 | this.roles = decodedToken['https://vega.com/roles'] || []; 45 | } 46 | } 47 | 48 | public isInRole(roleName) { 49 | return this.roles.indexOf(roleName) > -1; 50 | } 51 | 52 | public login() { 53 | // Call the show method to display the widget. 54 | this.lock.show(); 55 | } 56 | 57 | public authenticated() { 58 | // Check if there's an unexpired JWT 59 | // This searches for an item in localStorage with key == 'token' 60 | return tokenNotExpired('token'); 61 | } 62 | 63 | public logout() { 64 | // Remove token from localStorage 65 | localStorage.removeItem('token'); 66 | localStorage.removeItem('profile'); 67 | this.profile = null; 68 | this.roles = []; 69 | } 70 | } -------------------------------------------------------------------------------- /ClientApp/app/services/photo.service.ts: -------------------------------------------------------------------------------- 1 | import { Http } from '@angular/http'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | @Injectable() 5 | export class PhotoService { 6 | 7 | constructor(private http: Http) { } 8 | 9 | upload(vehicleId, photo) { 10 | var formData = new FormData(); 11 | formData.append('file', photo); 12 | return this.http.post(`/api/vehicles/${vehicleId}/photos`, formData) 13 | .map(res => res.json()); 14 | } 15 | 16 | getPhotos(vehicleId) { 17 | return this.http.get(`/api/vehicles/${vehicleId}/photos`) 18 | .map(res => res.json()); 19 | } 20 | } -------------------------------------------------------------------------------- /ClientApp/app/services/progress.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from "rxjs/Subject"; 3 | import { BrowserXhr } from "@angular/http"; 4 | 5 | @Injectable() 6 | export class ProgressService { 7 | private uploadProgress: Subject; 8 | 9 | startTracking() { 10 | this.uploadProgress = new Subject(); 11 | return this.uploadProgress; 12 | } 13 | 14 | notify(progress) { 15 | if (this.uploadProgress) 16 | this.uploadProgress.next(progress); 17 | } 18 | 19 | endTracking() { 20 | if (this.uploadProgress) 21 | this.uploadProgress.complete(); 22 | } 23 | } 24 | 25 | @Injectable() 26 | export class BrowserXhrWithProgress extends BrowserXhr { 27 | 28 | constructor(private service: ProgressService) { super(); } 29 | 30 | build(): XMLHttpRequest { 31 | var xhr: XMLHttpRequest = super.build(); 32 | 33 | xhr.upload.onprogress = (event) => { 34 | this.service.notify(this.createProgress(event)); 35 | }; 36 | 37 | xhr.upload.onloadend = () => { 38 | this.service.endTracking(); 39 | } 40 | 41 | return xhr; 42 | } 43 | 44 | private createProgress(event) { 45 | return { 46 | total: event.total, 47 | percentage: Math.round(event.loaded / event.total * 100) 48 | }; 49 | } 50 | } -------------------------------------------------------------------------------- /ClientApp/app/services/vehicle.service.ts: -------------------------------------------------------------------------------- 1 | import { SaveVehicle } from './../models/vehicle'; 2 | import { Injectable } from '@angular/core'; 3 | import { Http } from '@angular/http'; 4 | import 'rxjs/add/operator/map'; 5 | import { AuthHttp } from "angular2-jwt/angular2-jwt"; 6 | 7 | @Injectable() 8 | export class VehicleService { 9 | private readonly vehiclesEndpoint = '/api/vehicles'; 10 | 11 | constructor(private http: Http, private authHttp: AuthHttp) { } 12 | 13 | getFeatures() { 14 | return this.http.get('/api/features') 15 | .map(res => res.json()); 16 | } 17 | 18 | getMakes() { 19 | return this.http.get('/api/makes') 20 | .map(res => res.json()); 21 | } 22 | 23 | create(vehicle) { 24 | return this.authHttp.post(this.vehiclesEndpoint, vehicle) 25 | .map(res => res.json()); 26 | } 27 | 28 | getVehicle(id) { 29 | return this.http.get(this.vehiclesEndpoint + '/' + id) 30 | .map(res => res.json()); 31 | } 32 | 33 | getVehicles(filter) { 34 | return this.http.get(this.vehiclesEndpoint + '?' + this.toQueryString(filter)) 35 | .map(res => res.json()); 36 | } 37 | 38 | toQueryString(obj) { 39 | var parts = []; 40 | for (var property in obj) { 41 | var value = obj[property]; 42 | if (value != null && value != undefined) 43 | parts.push(encodeURIComponent(property) + '=' + encodeURIComponent(value)); 44 | } 45 | 46 | return parts.join('&'); 47 | } 48 | 49 | update(vehicle: SaveVehicle) { 50 | return this.authHttp.put(this.vehiclesEndpoint + '/' + vehicle.id, vehicle) 51 | .map(res => res.json()); 52 | } 53 | 54 | delete(id) { 55 | return this.authHttp.delete(this.vehiclesEndpoint + '/' + id) 56 | .map(res => res.json()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ClientApp/boot-client.ts: -------------------------------------------------------------------------------- 1 | import 'angular2-universal-polyfills/browser'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformUniversalDynamic } from 'angular2-universal'; 4 | import { AppModule } from './app/app.module'; 5 | import 'bootstrap'; 6 | const rootElemTagName = 'app'; // Update this if you change your root component selector 7 | 8 | // Enable either Hot Module Reloading or production mode 9 | if (module['hot']) { 10 | module['hot'].accept(); 11 | module['hot'].dispose(() => { 12 | // Before restarting the app, we create a new root element and dispose the old one 13 | const oldRootElem = document.querySelector(rootElemTagName); 14 | const newRootElem = document.createElement(rootElemTagName); 15 | oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem); 16 | platform.destroy(); 17 | }); 18 | } else { 19 | enableProdMode(); 20 | } 21 | 22 | // Boot the application, either now or when the DOM content is loaded 23 | const platform = platformUniversalDynamic(); 24 | const bootApplication = () => { platform.bootstrapModule(AppModule); }; 25 | if (document.readyState === 'complete') { 26 | bootApplication(); 27 | } else { 28 | document.addEventListener('DOMContentLoaded', bootApplication); 29 | } 30 | -------------------------------------------------------------------------------- /ClientApp/boot-server.ts: -------------------------------------------------------------------------------- 1 | import 'angular2-universal-polyfills'; 2 | import 'angular2-universal-patch'; 3 | import 'zone.js'; 4 | import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; 5 | import { enableProdMode } from '@angular/core'; 6 | import { platformNodeDynamic } from 'angular2-universal'; 7 | import { AppModule } from './app/app.module'; 8 | 9 | enableProdMode(); 10 | const platform = platformNodeDynamic(); 11 | 12 | export default createServerRenderer(params => { 13 | return new Promise((resolve, reject) => { 14 | const requestZone = Zone.current.fork({ 15 | name: 'angular-universal request', 16 | properties: { 17 | baseUrl: '/', 18 | requestUrl: params.url, 19 | originUrl: params.origin, 20 | preboot: false, 21 | document: '' 22 | }, 23 | onHandleError: (parentZone, currentZone, targetZone, error) => { 24 | // If any error occurs while rendering the module, reject the whole operation 25 | reject(error); 26 | return true; 27 | } 28 | }); 29 | 30 | return requestZone.run>(() => platform.serializeModule(AppModule)).then(html => { 31 | resolve({ html: html }); 32 | }, reject); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /Controllers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/Controllers/.DS_Store -------------------------------------------------------------------------------- /Controllers/AppPolicies.cs: -------------------------------------------------------------------------------- 1 | namespace vega.Controllers 2 | { 3 | public static class Policies 4 | { 5 | public const string RequireAdminRole = "RequireAdminRole"; 6 | } 7 | } -------------------------------------------------------------------------------- /Controllers/FeaturesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using AutoMapper; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using vega.Controllers.Resources; 8 | using vega.Core.Models; 9 | using vega.Persistence; 10 | 11 | namespace vega.Controllers 12 | { 13 | public class FeaturesController : Controller 14 | { 15 | private readonly VegaDbContext context; 16 | private readonly IMapper mapper; 17 | public FeaturesController(VegaDbContext context, IMapper mapper) 18 | { 19 | this.mapper = mapper; 20 | this.context = context; 21 | } 22 | 23 | [HttpGet("/api/features")] 24 | public async Task> GetFeatures() 25 | { 26 | var features = await context.Features.ToListAsync(); 27 | 28 | return mapper.Map, List>(features); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Vega.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult Error() 17 | { 18 | return View(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Controllers/MakesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using AutoMapper; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using vega.Controllers.Resources; 7 | using vega.Core.Models; 8 | using vega.Persistence; 9 | 10 | namespace vega.Controllers 11 | { 12 | public class MakesController : Controller 13 | { 14 | private readonly VegaDbContext context; 15 | private readonly IMapper mapper; 16 | public MakesController(VegaDbContext context, IMapper mapper) 17 | { 18 | this.mapper = mapper; 19 | this.context = context; 20 | } 21 | 22 | [HttpGet("/api/makes")] 23 | public async Task> GetMakes() 24 | { 25 | var makes = await context.Makes.Include(m => m.Models).ToListAsync(); 26 | 27 | return mapper.Map, List>(makes); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Controllers/PhotosController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using AutoMapper; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.Extensions.Options; 11 | using vega.Controllers.Resources; 12 | using vega.Core; 13 | using vega.Core.Models; 14 | 15 | namespace vega.Controllers 16 | { 17 | [Route("/api/vehicles/{vehicleId}/photos")] 18 | public class PhotosController : Controller 19 | { 20 | private readonly IHostingEnvironment host; 21 | private readonly IVehicleRepository vehicleRepository; 22 | private readonly IPhotoRepository photoRepository; 23 | private readonly IMapper mapper; 24 | private readonly PhotoSettings photoSettings; 25 | private readonly IPhotoService photoService; 26 | 27 | public PhotosController(IHostingEnvironment host, IVehicleRepository vehicleRepository, IPhotoRepository photoRepository, IMapper mapper, IOptionsSnapshot options, IPhotoService photoService) 28 | { 29 | this.photoService = photoService; 30 | this.photoSettings = options.Value; 31 | this.mapper = mapper; 32 | this.vehicleRepository = vehicleRepository; 33 | this.photoRepository = photoRepository; 34 | this.host = host; 35 | } 36 | 37 | [HttpGet] 38 | public async Task> GetPhotos(int vehicleId) 39 | { 40 | var photos = await photoRepository.GetPhotos(vehicleId); 41 | 42 | return mapper.Map, IEnumerable>(photos); 43 | } 44 | 45 | [HttpPost] 46 | public async Task Upload(int vehicleId, IFormFile file) 47 | { 48 | var vehicle = await vehicleRepository.GetVehicle(vehicleId, includeRelated: false); 49 | if (vehicle == null) 50 | return NotFound(); 51 | 52 | if (file == null) return BadRequest("Null file"); 53 | if (file.Length == 0) return BadRequest("Empty file"); 54 | if (file.Length > photoSettings.MaxBytes) return BadRequest("Max file size exceeded"); 55 | if (!photoSettings.IsSupported(file.FileName)) return BadRequest("Invalid file type."); 56 | 57 | var uploadsFolderPath = Path.Combine(host.WebRootPath, "uploads"); 58 | var photo = await photoService.UploadPhoto(vehicle, file, uploadsFolderPath); 59 | 60 | return Ok(mapper.Map(photo)); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Controllers/Resources/ContactResource.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace vega.Controllers.Resources 4 | { 5 | public class ContactResource 6 | { 7 | [Required] 8 | [StringLength(255)] 9 | public string Name { get; set; } 10 | 11 | [StringLength(255)] 12 | public string Email { get; set; } 13 | 14 | [Required] 15 | [StringLength(255)] 16 | public string Phone { get; set; } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /Controllers/Resources/KeyValuePairResource.cs: -------------------------------------------------------------------------------- 1 | namespace vega.Controllers.Resources 2 | { 3 | public class KeyValuePairResource 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Controllers/Resources/MakeResource.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace vega.Controllers.Resources 5 | { 6 | public class MakeResource : KeyValuePairResource 7 | { 8 | public ICollection Models { get; set; } 9 | 10 | public MakeResource() 11 | { 12 | Models = new Collection(); 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /Controllers/Resources/PhotoResource.cs: -------------------------------------------------------------------------------- 1 | namespace vega.Controllers.Resources 2 | { 3 | public class PhotoResource 4 | { 5 | public int Id { get; set; } 6 | 7 | public string FileName { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Controllers/Resources/QueryResultResource.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace vega.Controllers.Resources 4 | { 5 | public class QueryResultResource 6 | { 7 | public int TotalItems { get; set; } 8 | public IEnumerable Items { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Controllers/Resources/SaveVehicleResource.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace vega.Controllers.Resources 6 | { 7 | public class SaveVehicleResource 8 | { 9 | public int Id { get; set; } 10 | public int ModelId { get; set; } 11 | public bool IsRegistered { get; set; } 12 | 13 | [Required] 14 | public ContactResource Contact { get; set; } 15 | public ICollection Features { get; set; } 16 | 17 | public SaveVehicleResource() 18 | { 19 | Features = new Collection(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Controllers/Resources/VehicleQueryResource.cs: -------------------------------------------------------------------------------- 1 | namespace vega.Controllers.Resources 2 | { 3 | public class VehicleQueryResource 4 | { 5 | public int? MakeId { get; set; } 6 | public int? ModelId { get; set; } 7 | public string SortBy { get; set; } 8 | public bool IsSortAscending { get; set; } 9 | public int Page { get; set; } 10 | public byte PageSize { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Controllers/Resources/VehicleResource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace vega.Controllers.Resources 6 | { 7 | public class VehicleResource 8 | { 9 | public int Id { get; set; } 10 | public KeyValuePairResource Model { get; set; } 11 | public KeyValuePairResource Make { get; set; } 12 | public bool IsRegistered { get; set; } 13 | public ContactResource Contact { get; set; } 14 | public DateTime LastUpdate { get; set; } 15 | public ICollection Features { get; set; } 16 | 17 | public VehicleResource() 18 | { 19 | Features = new Collection(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Controllers/SampleDataController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Vega.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class SampleDataController : Controller 11 | { 12 | private static string[] Summaries = new[] 13 | { 14 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 15 | }; 16 | 17 | [HttpGet("[action]")] 18 | public IEnumerable WeatherForecasts() 19 | { 20 | var rng = new Random(); 21 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 22 | { 23 | DateFormatted = DateTime.Now.AddDays(index).ToString("d"), 24 | TemperatureC = rng.Next(-20, 55), 25 | Summary = Summaries[rng.Next(Summaries.Length)] 26 | }); 27 | } 28 | 29 | public class WeatherForecast 30 | { 31 | public string DateFormatted { get; set; } 32 | public int TemperatureC { get; set; } 33 | public string Summary { get; set; } 34 | 35 | public int TemperatureF 36 | { 37 | get 38 | { 39 | return 32 + (int)(TemperatureC / 0.5556); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Controllers/VehiclesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using AutoMapper; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using vega.Controllers.Resources; 8 | using vega.Core.Models; 9 | using vega.Core; 10 | using Microsoft.AspNetCore.Authorization; 11 | 12 | namespace vega.Controllers 13 | { 14 | [Route("/api/vehicles")] 15 | public class VehiclesController : Controller 16 | { 17 | private readonly IMapper mapper; 18 | private readonly IVehicleRepository repository; 19 | private readonly IUnitOfWork unitOfWork; 20 | 21 | public VehiclesController(IMapper mapper, IVehicleRepository repository, IUnitOfWork unitOfWork) 22 | { 23 | this.unitOfWork = unitOfWork; 24 | this.repository = repository; 25 | this.mapper = mapper; 26 | } 27 | 28 | [HttpPost] 29 | [Authorize] 30 | public async Task CreateVehicle([FromBody] SaveVehicleResource vehicleResource) 31 | { 32 | if (!ModelState.IsValid) 33 | return BadRequest(ModelState); 34 | 35 | var vehicle = mapper.Map(vehicleResource); 36 | vehicle.LastUpdate = DateTime.Now; 37 | 38 | repository.Add(vehicle); 39 | await unitOfWork.CompleteAsync(); 40 | 41 | vehicle = await repository.GetVehicle(vehicle.Id); 42 | 43 | var result = mapper.Map(vehicle); 44 | 45 | return Ok(result); 46 | } 47 | 48 | [HttpPut("{id}")] 49 | [Authorize] 50 | public async Task UpdateVehicle(int id, [FromBody] SaveVehicleResource vehicleResource) 51 | { 52 | if (!ModelState.IsValid) 53 | return BadRequest(ModelState); 54 | 55 | var vehicle = await repository.GetVehicle(id); 56 | 57 | if (vehicle == null) 58 | return NotFound(); 59 | 60 | mapper.Map(vehicleResource, vehicle); 61 | vehicle.LastUpdate = DateTime.Now; 62 | 63 | await unitOfWork.CompleteAsync(); 64 | 65 | vehicle = await repository.GetVehicle(vehicle.Id); 66 | var result = mapper.Map(vehicle); 67 | 68 | return Ok(result); 69 | } 70 | 71 | [HttpDelete("{id}")] 72 | [Authorize] 73 | public async Task DeleteVehicle(int id) 74 | { 75 | var vehicle = await repository.GetVehicle(id, includeRelated: false); 76 | 77 | if (vehicle == null) 78 | return NotFound(); 79 | 80 | repository.Remove(vehicle); 81 | await unitOfWork.CompleteAsync(); 82 | 83 | return Ok(id); 84 | } 85 | 86 | [HttpGet("{id}")] 87 | public async Task GetVehicle(int id) 88 | { 89 | var vehicle = await repository.GetVehicle(id); 90 | 91 | if (vehicle == null) 92 | return NotFound(); 93 | 94 | var vehicleResource = mapper.Map(vehicle); 95 | 96 | return Ok(vehicleResource); 97 | } 98 | 99 | [HttpGet] 100 | public async Task> GetVehicles(VehicleQueryResource filterResource) 101 | { 102 | var filter = mapper.Map(filterResource); 103 | var queryResult = await repository.GetVehicles(filter); 104 | 105 | return mapper.Map, QueryResultResource>(queryResult); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Core/FileSystemPhotoStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace vega.Core 7 | { 8 | public class FileSystemPhotoStorage : IPhotoStorage 9 | { 10 | public async Task StorePhoto(string uploadsFolderPath, IFormFile file) 11 | { 12 | if (!Directory.Exists(uploadsFolderPath)) 13 | Directory.CreateDirectory(uploadsFolderPath); 14 | 15 | var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName); 16 | var filePath = Path.Combine(uploadsFolderPath, fileName); 17 | 18 | using (var stream = new FileStream(filePath, FileMode.Create)) 19 | { 20 | await file.CopyToAsync(stream); 21 | } 22 | 23 | return fileName; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Core/IPhotoRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using vega.Core.Models; 4 | 5 | namespace vega.Core 6 | { 7 | public interface IPhotoRepository 8 | { 9 | Task> GetPhotos(int vehicleId); 10 | } 11 | } -------------------------------------------------------------------------------- /Core/IPhotoService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Http; 3 | using vega.Core.Models; 4 | 5 | namespace vega.Core 6 | { 7 | public interface IPhotoService 8 | { 9 | Task UploadPhoto(Vehicle vehicle, IFormFile file, string uploadsFolderPath); 10 | } 11 | } -------------------------------------------------------------------------------- /Core/IPhotoStorage.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace vega.Core 5 | { 6 | public interface IPhotoStorage 7 | { 8 | Task StorePhoto(string uploadsFolderPath, IFormFile file); 9 | } 10 | } -------------------------------------------------------------------------------- /Core/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace vega.Core 5 | { 6 | public interface IUnitOfWork 7 | { 8 | Task CompleteAsync(); 9 | } 10 | } -------------------------------------------------------------------------------- /Core/IVehicleRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using vega.Core.Models; 4 | 5 | namespace vega.Core 6 | { 7 | public interface IVehicleRepository 8 | { 9 | Task GetVehicle(int id, bool includeRelated = true); 10 | void Add(Vehicle vehicle); 11 | void Remove(Vehicle vehicle); 12 | Task> GetVehicles(VehicleQuery filter); 13 | } 14 | } -------------------------------------------------------------------------------- /Core/Models/Feature.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace vega.Core.Models 4 | { 5 | public class Feature 6 | { 7 | public int Id { get; set; } 8 | 9 | [Required] 10 | [StringLength(255)] 11 | public string Name { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Core/Models/Make.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace vega.Core.Models 6 | { 7 | public class Make 8 | { 9 | public int Id { get; set; } 10 | [Required] 11 | [StringLength(255)] 12 | public string Name { get; set; } 13 | public ICollection Models { get; set; } 14 | 15 | public Make() 16 | { 17 | Models = new Collection(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Core/Models/Model.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace vega.Core.Models 5 | { 6 | [Table("Models")] 7 | public class Model 8 | { 9 | public int Id { get; set; } 10 | 11 | [Required] 12 | [StringLength(255)] 13 | public string Name { get; set; } 14 | 15 | public Make Make { get; set; } 16 | 17 | public int MakeId { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Core/Models/Photo.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace vega.Core.Models 4 | { 5 | public class Photo 6 | { 7 | public int Id { get; set; } 8 | 9 | [Required] 10 | [StringLength(255)] 11 | public string FileName { get; set; } 12 | 13 | public int VehicleId { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Core/Models/PhotoSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | 4 | namespace vega.Core.Models 5 | { 6 | public class PhotoSettings 7 | { 8 | public int MaxBytes { get; set; } 9 | public string[] AcceptedFileTypes { get; set; } 10 | 11 | public bool IsSupported(string fileName) 12 | { 13 | return AcceptedFileTypes.Any(s => s == Path.GetExtension(fileName).ToLower()); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Core/Models/QueryResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace vega.Core.Models 4 | { 5 | public class QueryResult 6 | { 7 | public int TotalItems { get; set; } 8 | public IEnumerable Items { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Core/Models/Vehicle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.ComponentModel.DataAnnotations.Schema; 6 | 7 | namespace vega.Core.Models 8 | { 9 | [Table("Vehicles")] 10 | public class Vehicle 11 | { 12 | public int Id { get; set; } 13 | public int ModelId { get; set; } 14 | public Model Model { get; set; } 15 | public bool IsRegistered { get; set; } 16 | [Required] 17 | [StringLength(255)] 18 | public string ContactName { get; set; } 19 | 20 | [StringLength(255)] 21 | public string ContactEmail { get; set; } 22 | 23 | [Required] 24 | [StringLength(255)] 25 | public string ContactPhone { get; set; } 26 | public DateTime LastUpdate { get; set; } 27 | public ICollection Features { get; set; } 28 | public ICollection Photos { get; set; } 29 | 30 | public Vehicle() 31 | { 32 | Features = new Collection(); 33 | Photos = new Collection(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Core/Models/VehicleFeature.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | 3 | namespace vega.Core.Models 4 | { 5 | [Table("VehicleFeatures")] 6 | public class VehicleFeature 7 | { 8 | public int VehicleId { get; set; } 9 | public int FeatureId { get; set; } 10 | public Vehicle Vehicle { get; set; } 11 | public Feature Feature { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Core/Models/VehicleQuery.cs: -------------------------------------------------------------------------------- 1 | using vega.Extensions; 2 | 3 | namespace vega.Core.Models 4 | { 5 | public class VehicleQuery : IQueryObject 6 | { 7 | public int? MakeId { get; set; } 8 | public int? ModelId { get; set; } 9 | public string SortBy { get; set; } 10 | public bool IsSortAscending { get; set; } 11 | public int Page { get; set; } 12 | public byte PageSize { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Core/PhotoService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using vega.Core.Models; 6 | 7 | namespace vega.Core 8 | { 9 | public class PhotoService : IPhotoService 10 | { 11 | private readonly IUnitOfWork unitOfWork; 12 | private readonly IPhotoStorage photoStorage; 13 | public PhotoService(IUnitOfWork unitOfWork, IPhotoStorage photoStorage) 14 | { 15 | this.photoStorage = photoStorage; 16 | this.unitOfWork = unitOfWork; 17 | } 18 | 19 | public async Task UploadPhoto(Vehicle vehicle, IFormFile file, string uploadsFolderPath) 20 | { 21 | var fileName = await photoStorage.StorePhoto(uploadsFolderPath, file); 22 | 23 | var photo = new Photo { FileName = fileName }; 24 | vehicle.Photos.Add(photo); 25 | await unitOfWork.CompleteAsync(); 26 | 27 | return photo; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Extensions/IQueryObject.cs: -------------------------------------------------------------------------------- 1 | namespace vega.Extensions 2 | { 3 | public interface IQueryObject 4 | { 5 | string SortBy { get; set; } 6 | bool IsSortAscending { get; set; } 7 | int Page { get; set; } 8 | byte PageSize { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Extensions/IQueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using vega.Core.Models; 6 | 7 | namespace vega.Extensions 8 | { 9 | public static class IQueryableExtensions 10 | { 11 | 12 | public static IQueryable ApplyFiltering(this IQueryable query, VehicleQuery queryObj) 13 | { 14 | if (queryObj.MakeId.HasValue) 15 | query = query.Where(v => v.Model.MakeId == queryObj.MakeId.Value); 16 | 17 | if (queryObj.ModelId.HasValue) 18 | query = query.Where(v => v.ModelId == queryObj.ModelId.Value); 19 | 20 | return query; 21 | } 22 | 23 | public static IQueryable ApplyOrdering(this IQueryable query, IQueryObject queryObj, Dictionary>> columnsMap) 24 | { 25 | if (String.IsNullOrWhiteSpace(queryObj.SortBy) || !columnsMap.ContainsKey(queryObj.SortBy)) 26 | return query; 27 | 28 | if (queryObj.IsSortAscending) 29 | return query.OrderBy(columnsMap[queryObj.SortBy]); 30 | else 31 | return query.OrderByDescending(columnsMap[queryObj.SortBy]); 32 | } 33 | 34 | public static IQueryable ApplyPaging(this IQueryable query, IQueryObject queryObj) 35 | { 36 | if (queryObj.Page <= 0) 37 | queryObj.Page = 1; 38 | 39 | if (queryObj.PageSize <= 0) 40 | queryObj.PageSize = 10; 41 | 42 | return query.Skip((queryObj.Page - 1) * queryObj.PageSize).Take(queryObj.PageSize); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Mapping/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using System.Linq; 3 | using vega.Controllers.Resources; 4 | using vega.Core.Models; 5 | 6 | namespace vega.Mapping 7 | { 8 | public class MappingProfile : Profile 9 | { 10 | public MappingProfile() 11 | { 12 | // Domain to API Resource 13 | CreateMap(); 14 | CreateMap(typeof(QueryResult<>), typeof(QueryResultResource<>)); 15 | CreateMap(); 16 | CreateMap(); 17 | CreateMap(); 18 | CreateMap(); 19 | CreateMap() 20 | .ForMember(vr => vr.Contact, opt => opt.MapFrom(v => new ContactResource { Name = v.ContactName, Email = v.ContactEmail, Phone = v.ContactPhone } )) 21 | .ForMember(vr => vr.Features, opt => opt.MapFrom(v => v.Features.Select(vf => vf.FeatureId))); 22 | CreateMap() 23 | .ForMember(vr => vr.Make, opt => opt.MapFrom(v => v.Model.Make)) 24 | .ForMember(vr => vr.Contact, opt => opt.MapFrom(v => new ContactResource { Name = v.ContactName, Email = v.ContactEmail, Phone = v.ContactPhone } )) 25 | .ForMember(vr => vr.Features, opt => opt.MapFrom(v => v.Features.Select(vf => new KeyValuePairResource { Id = vf.Feature.Id, Name = vf.Feature.Name }))); 26 | 27 | // API Resource to Domain 28 | CreateMap(); 29 | CreateMap() 30 | .ForMember(v => v.Id, opt => opt.Ignore()) 31 | .ForMember(v => v.ContactName, opt => opt.MapFrom(vr => vr.Contact.Name)) 32 | .ForMember(v => v.ContactEmail, opt => opt.MapFrom(vr => vr.Contact.Email)) 33 | .ForMember(v => v.ContactPhone, opt => opt.MapFrom(vr => vr.Contact.Phone)) 34 | .ForMember(v => v.Features, opt => opt.Ignore()) 35 | .AfterMap((vr, v) => { 36 | // Remove unselected features 37 | var removedFeatures = v.Features.Where(f => !vr.Features.Contains(f.FeatureId)).ToList(); 38 | foreach (var f in removedFeatures) 39 | v.Features.Remove(f); 40 | 41 | // Add new features 42 | var addedFeatures = vr.Features.Where(id => !v.Features.Any(f => f.FeatureId == id)).Select(id => new VehicleFeature { FeatureId = id }).ToList(); 43 | foreach (var f in addedFeatures) 44 | v.Features.Add(f); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Migrations/20170320232928_InitialModel.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170320232928_InitialModel")] 12 | partial class InitialModel 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Make", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name"); 26 | 27 | b.HasKey("Id"); 28 | 29 | b.ToTable("Makes"); 30 | }); 31 | 32 | modelBuilder.Entity("vega.Models.Model", b => 33 | { 34 | b.Property("Id") 35 | .ValueGeneratedOnAdd(); 36 | 37 | b.Property("MakeId"); 38 | 39 | b.Property("Name"); 40 | 41 | b.HasKey("Id"); 42 | 43 | b.HasIndex("MakeId"); 44 | 45 | b.ToTable("Model"); 46 | }); 47 | 48 | modelBuilder.Entity("vega.Models.Model", b => 49 | { 50 | b.HasOne("vega.Models.Make", "Make") 51 | .WithMany("Models") 52 | .HasForeignKey("MakeId") 53 | .OnDelete(DeleteBehavior.Cascade); 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Migrations/20170320232928_InitialModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace Vega.Migrations 7 | { 8 | public partial class InitialModel : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Makes", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | Name = table.Column(nullable: true) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_Makes", x => x.Id); 23 | }); 24 | 25 | migrationBuilder.CreateTable( 26 | name: "Model", 27 | columns: table => new 28 | { 29 | Id = table.Column(nullable: false) 30 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 31 | MakeId = table.Column(nullable: false), 32 | Name = table.Column(nullable: true) 33 | }, 34 | constraints: table => 35 | { 36 | table.PrimaryKey("PK_Model", x => x.Id); 37 | table.ForeignKey( 38 | name: "FK_Model_Makes_MakeId", 39 | column: x => x.MakeId, 40 | principalTable: "Makes", 41 | principalColumn: "Id", 42 | onDelete: ReferentialAction.Cascade); 43 | }); 44 | 45 | migrationBuilder.CreateIndex( 46 | name: "IX_Model_MakeId", 47 | table: "Model", 48 | column: "MakeId"); 49 | } 50 | 51 | protected override void Down(MigrationBuilder migrationBuilder) 52 | { 53 | migrationBuilder.DropTable( 54 | name: "Model"); 55 | 56 | migrationBuilder.DropTable( 57 | name: "Makes"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Migrations/20170321005120_ApplyConstraints.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170321005120_ApplyConstraints")] 12 | partial class ApplyConstraints 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Make", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Makes"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Models.Model", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("MakeId"); 40 | 41 | b.Property("Name") 42 | .IsRequired() 43 | .HasMaxLength(255); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("MakeId"); 48 | 49 | b.ToTable("Models"); 50 | }); 51 | 52 | modelBuilder.Entity("vega.Models.Model", b => 53 | { 54 | b.HasOne("vega.Models.Make", "Make") 55 | .WithMany("Models") 56 | .HasForeignKey("MakeId") 57 | .OnDelete(DeleteBehavior.Cascade); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Migrations/20170321005120_ApplyConstraints.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace Vega.Migrations 6 | { 7 | public partial class ApplyConstraints : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.DropForeignKey( 12 | name: "FK_Model_Makes_MakeId", 13 | table: "Model"); 14 | 15 | migrationBuilder.DropPrimaryKey( 16 | name: "PK_Model", 17 | table: "Model"); 18 | 19 | migrationBuilder.RenameTable( 20 | name: "Model", 21 | newName: "Models"); 22 | 23 | migrationBuilder.RenameIndex( 24 | name: "IX_Model_MakeId", 25 | table: "Models", 26 | newName: "IX_Models_MakeId"); 27 | 28 | migrationBuilder.AlterColumn( 29 | name: "Name", 30 | table: "Models", 31 | maxLength: 255, 32 | nullable: false, 33 | oldClrType: typeof(string), 34 | oldNullable: true); 35 | 36 | migrationBuilder.AlterColumn( 37 | name: "Name", 38 | table: "Makes", 39 | maxLength: 255, 40 | nullable: false, 41 | oldClrType: typeof(string), 42 | oldNullable: true); 43 | 44 | migrationBuilder.AddPrimaryKey( 45 | name: "PK_Models", 46 | table: "Models", 47 | column: "Id"); 48 | 49 | migrationBuilder.AddForeignKey( 50 | name: "FK_Models_Makes_MakeId", 51 | table: "Models", 52 | column: "MakeId", 53 | principalTable: "Makes", 54 | principalColumn: "Id", 55 | onDelete: ReferentialAction.Cascade); 56 | } 57 | 58 | protected override void Down(MigrationBuilder migrationBuilder) 59 | { 60 | migrationBuilder.DropForeignKey( 61 | name: "FK_Models_Makes_MakeId", 62 | table: "Models"); 63 | 64 | migrationBuilder.DropPrimaryKey( 65 | name: "PK_Models", 66 | table: "Models"); 67 | 68 | migrationBuilder.RenameTable( 69 | name: "Models", 70 | newName: "Model"); 71 | 72 | migrationBuilder.RenameIndex( 73 | name: "IX_Models_MakeId", 74 | table: "Model", 75 | newName: "IX_Model_MakeId"); 76 | 77 | migrationBuilder.AlterColumn( 78 | name: "Name", 79 | table: "Model", 80 | nullable: true, 81 | oldClrType: typeof(string), 82 | oldMaxLength: 255); 83 | 84 | migrationBuilder.AlterColumn( 85 | name: "Name", 86 | table: "Makes", 87 | nullable: true, 88 | oldClrType: typeof(string), 89 | oldMaxLength: 255); 90 | 91 | migrationBuilder.AddPrimaryKey( 92 | name: "PK_Model", 93 | table: "Model", 94 | column: "Id"); 95 | 96 | migrationBuilder.AddForeignKey( 97 | name: "FK_Model_Makes_MakeId", 98 | table: "Model", 99 | column: "MakeId", 100 | principalTable: "Makes", 101 | principalColumn: "Id", 102 | onDelete: ReferentialAction.Cascade); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Migrations/20170321005742_SeedDatabase.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170321005742_SeedDatabase")] 12 | partial class SeedDatabase 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Make", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Makes"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Models.Model", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("MakeId"); 40 | 41 | b.Property("Name") 42 | .IsRequired() 43 | .HasMaxLength(255); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("MakeId"); 48 | 49 | b.ToTable("Models"); 50 | }); 51 | 52 | modelBuilder.Entity("vega.Models.Model", b => 53 | { 54 | b.HasOne("vega.Models.Make", "Make") 55 | .WithMany("Models") 56 | .HasForeignKey("MakeId") 57 | .OnDelete(DeleteBehavior.Cascade); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Migrations/20170321005742_SeedDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace Vega.Migrations 6 | { 7 | public partial class SeedDatabase : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.Sql("INSERT INTO Makes (Name) VALUES ('Make1')"); 12 | migrationBuilder.Sql("INSERT INTO Makes (Name) VALUES ('Make2')"); 13 | migrationBuilder.Sql("INSERT INTO Makes (Name) VALUES ('Make3')"); 14 | 15 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make1-ModelA', (SELECT ID FROM Makes WHERE Name = 'Make1'))"); 16 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make1-ModelB', (SELECT ID FROM Makes WHERE Name = 'Make1'))"); 17 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make1-ModelC', (SELECT ID FROM Makes WHERE Name = 'Make1'))"); 18 | 19 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make2-ModelA', (SELECT ID FROM Makes WHERE Name = 'Make2'))"); 20 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make2-ModelB', (SELECT ID FROM Makes WHERE Name = 'Make2'))"); 21 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make2-ModelC', (SELECT ID FROM Makes WHERE Name = 'Make2'))"); 22 | 23 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make3-ModelA', (SELECT ID FROM Makes WHERE Name = 'Make3'))"); 24 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make3-ModelB', (SELECT ID FROM Makes WHERE Name = 'Make3'))"); 25 | migrationBuilder.Sql("INSERT INTO Models (Name, MakeID) VALUES ('Make3-ModelC', (SELECT ID FROM Makes WHERE Name = 'Make3'))"); 26 | 27 | } 28 | 29 | protected override void Down(MigrationBuilder migrationBuilder) 30 | { 31 | migrationBuilder.Sql("DELETE FROM Makes WHERE Name IN ('Make1', 'Make2', 'Make3')"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Migrations/20170322022552_AddFeature.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170322022552_AddFeature")] 12 | partial class AddFeature 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Feature", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Features"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Models.Make", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("Name") 40 | .IsRequired() 41 | .HasMaxLength(255); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("Makes"); 46 | }); 47 | 48 | modelBuilder.Entity("vega.Models.Model", b => 49 | { 50 | b.Property("Id") 51 | .ValueGeneratedOnAdd(); 52 | 53 | b.Property("MakeId"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasMaxLength(255); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("MakeId"); 62 | 63 | b.ToTable("Models"); 64 | }); 65 | 66 | modelBuilder.Entity("vega.Models.Model", b => 67 | { 68 | b.HasOne("vega.Models.Make", "Make") 69 | .WithMany("Models") 70 | .HasForeignKey("MakeId") 71 | .OnDelete(DeleteBehavior.Cascade); 72 | }); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Migrations/20170322022552_AddFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace Vega.Migrations 7 | { 8 | public partial class AddFeature : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Features", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | Name = table.Column(maxLength: 255, nullable: false) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_Features", x => x.Id); 23 | }); 24 | } 25 | 26 | protected override void Down(MigrationBuilder migrationBuilder) 27 | { 28 | migrationBuilder.DropTable( 29 | name: "Features"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Migrations/20170322022625_SeedFeatures.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170322022625_SeedFeatures")] 12 | partial class SeedFeatures 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Feature", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Features"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Models.Make", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("Name") 40 | .IsRequired() 41 | .HasMaxLength(255); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("Makes"); 46 | }); 47 | 48 | modelBuilder.Entity("vega.Models.Model", b => 49 | { 50 | b.Property("Id") 51 | .ValueGeneratedOnAdd(); 52 | 53 | b.Property("MakeId"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasMaxLength(255); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("MakeId"); 62 | 63 | b.ToTable("Models"); 64 | }); 65 | 66 | modelBuilder.Entity("vega.Models.Model", b => 67 | { 68 | b.HasOne("vega.Models.Make", "Make") 69 | .WithMany("Models") 70 | .HasForeignKey("MakeId") 71 | .OnDelete(DeleteBehavior.Cascade); 72 | }); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Migrations/20170322022625_SeedFeatures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace Vega.Migrations 6 | { 7 | public partial class SeedFeatures : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.Sql("INSERT INTO Features (Name) VALUES ('Feature1')"); 12 | migrationBuilder.Sql("INSERT INTO Features (Name) VALUES ('Feature2')"); 13 | migrationBuilder.Sql("INSERT INTO Features (Name) VALUES ('Feature3')"); 14 | } 15 | 16 | protected override void Down(MigrationBuilder migrationBuilder) 17 | { 18 | migrationBuilder.Sql("DELETE FROM Features WHERE Name IN ('Feature1', 'Feature2', 'Feature3')"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Migrations/20170326223931_AddVehicle.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170326223931_AddVehicle")] 12 | partial class AddVehicle 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Models.Feature", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Features"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Models.Make", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("Name") 40 | .IsRequired() 41 | .HasMaxLength(255); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("Makes"); 46 | }); 47 | 48 | modelBuilder.Entity("vega.Models.Model", b => 49 | { 50 | b.Property("Id") 51 | .ValueGeneratedOnAdd(); 52 | 53 | b.Property("MakeId"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasMaxLength(255); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("MakeId"); 62 | 63 | b.ToTable("Models"); 64 | }); 65 | 66 | modelBuilder.Entity("vega.Models.Vehicle", b => 67 | { 68 | b.Property("Id") 69 | .ValueGeneratedOnAdd(); 70 | 71 | b.Property("ContactEmail") 72 | .HasMaxLength(255); 73 | 74 | b.Property("ContactName") 75 | .IsRequired() 76 | .HasMaxLength(255); 77 | 78 | b.Property("ContactPhone") 79 | .IsRequired() 80 | .HasMaxLength(255); 81 | 82 | b.Property("IsRegistered"); 83 | 84 | b.Property("LastUpdate"); 85 | 86 | b.Property("ModelId"); 87 | 88 | b.HasKey("Id"); 89 | 90 | b.HasIndex("ModelId"); 91 | 92 | b.ToTable("Vehicles"); 93 | }); 94 | 95 | modelBuilder.Entity("vega.Models.VehicleFeature", b => 96 | { 97 | b.Property("VehicleId"); 98 | 99 | b.Property("FeatureId"); 100 | 101 | b.HasKey("VehicleId", "FeatureId"); 102 | 103 | b.HasIndex("FeatureId"); 104 | 105 | b.ToTable("VehicleFeatures"); 106 | }); 107 | 108 | modelBuilder.Entity("vega.Models.Model", b => 109 | { 110 | b.HasOne("vega.Models.Make", "Make") 111 | .WithMany("Models") 112 | .HasForeignKey("MakeId") 113 | .OnDelete(DeleteBehavior.Cascade); 114 | }); 115 | 116 | modelBuilder.Entity("vega.Models.Vehicle", b => 117 | { 118 | b.HasOne("vega.Models.Model", "Model") 119 | .WithMany() 120 | .HasForeignKey("ModelId") 121 | .OnDelete(DeleteBehavior.Cascade); 122 | }); 123 | 124 | modelBuilder.Entity("vega.Models.VehicleFeature", b => 125 | { 126 | b.HasOne("vega.Models.Feature", "Feature") 127 | .WithMany() 128 | .HasForeignKey("FeatureId") 129 | .OnDelete(DeleteBehavior.Cascade); 130 | 131 | b.HasOne("vega.Models.Vehicle", "Vehicle") 132 | .WithMany("Features") 133 | .HasForeignKey("VehicleId") 134 | .OnDelete(DeleteBehavior.Cascade); 135 | }); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Migrations/20170326223931_AddVehicle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace Vega.Migrations 7 | { 8 | public partial class AddVehicle : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Vehicles", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | ContactEmail = table.Column(maxLength: 255, nullable: true), 19 | ContactName = table.Column(maxLength: 255, nullable: false), 20 | ContactPhone = table.Column(maxLength: 255, nullable: false), 21 | IsRegistered = table.Column(nullable: false), 22 | LastUpdate = table.Column(nullable: false), 23 | ModelId = table.Column(nullable: false) 24 | }, 25 | constraints: table => 26 | { 27 | table.PrimaryKey("PK_Vehicles", x => x.Id); 28 | table.ForeignKey( 29 | name: "FK_Vehicles_Models_ModelId", 30 | column: x => x.ModelId, 31 | principalTable: "Models", 32 | principalColumn: "Id", 33 | onDelete: ReferentialAction.Cascade); 34 | }); 35 | 36 | migrationBuilder.CreateTable( 37 | name: "VehicleFeatures", 38 | columns: table => new 39 | { 40 | VehicleId = table.Column(nullable: false), 41 | FeatureId = table.Column(nullable: false) 42 | }, 43 | constraints: table => 44 | { 45 | table.PrimaryKey("PK_VehicleFeatures", x => new { x.VehicleId, x.FeatureId }); 46 | table.ForeignKey( 47 | name: "FK_VehicleFeatures_Features_FeatureId", 48 | column: x => x.FeatureId, 49 | principalTable: "Features", 50 | principalColumn: "Id", 51 | onDelete: ReferentialAction.Cascade); 52 | table.ForeignKey( 53 | name: "FK_VehicleFeatures_Vehicles_VehicleId", 54 | column: x => x.VehicleId, 55 | principalTable: "Vehicles", 56 | principalColumn: "Id", 57 | onDelete: ReferentialAction.Cascade); 58 | }); 59 | 60 | migrationBuilder.CreateIndex( 61 | name: "IX_Vehicles_ModelId", 62 | table: "Vehicles", 63 | column: "ModelId"); 64 | 65 | migrationBuilder.CreateIndex( 66 | name: "IX_VehicleFeatures_FeatureId", 67 | table: "VehicleFeatures", 68 | column: "FeatureId"); 69 | } 70 | 71 | protected override void Down(MigrationBuilder migrationBuilder) 72 | { 73 | migrationBuilder.DropTable( 74 | name: "VehicleFeatures"); 75 | 76 | migrationBuilder.DropTable( 77 | name: "Vehicles"); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Migrations/20170412005215_AddPhoto.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | [Migration("20170412005215_AddPhoto")] 12 | partial class AddPhoto 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("vega.Core.Models.Feature", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("Name") 26 | .IsRequired() 27 | .HasMaxLength(255); 28 | 29 | b.HasKey("Id"); 30 | 31 | b.ToTable("Features"); 32 | }); 33 | 34 | modelBuilder.Entity("vega.Core.Models.Make", b => 35 | { 36 | b.Property("Id") 37 | .ValueGeneratedOnAdd(); 38 | 39 | b.Property("Name") 40 | .IsRequired() 41 | .HasMaxLength(255); 42 | 43 | b.HasKey("Id"); 44 | 45 | b.ToTable("Makes"); 46 | }); 47 | 48 | modelBuilder.Entity("vega.Core.Models.Model", b => 49 | { 50 | b.Property("Id") 51 | .ValueGeneratedOnAdd(); 52 | 53 | b.Property("MakeId"); 54 | 55 | b.Property("Name") 56 | .IsRequired() 57 | .HasMaxLength(255); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("MakeId"); 62 | 63 | b.ToTable("Models"); 64 | }); 65 | 66 | modelBuilder.Entity("vega.Core.Models.Photo", b => 67 | { 68 | b.Property("Id") 69 | .ValueGeneratedOnAdd(); 70 | 71 | b.Property("FileName") 72 | .IsRequired() 73 | .HasMaxLength(255); 74 | 75 | b.Property("VehicleId"); 76 | 77 | b.HasKey("Id"); 78 | 79 | b.HasIndex("VehicleId"); 80 | 81 | b.ToTable("Photos"); 82 | }); 83 | 84 | modelBuilder.Entity("vega.Core.Models.Vehicle", b => 85 | { 86 | b.Property("Id") 87 | .ValueGeneratedOnAdd(); 88 | 89 | b.Property("ContactEmail") 90 | .HasMaxLength(255); 91 | 92 | b.Property("ContactName") 93 | .IsRequired() 94 | .HasMaxLength(255); 95 | 96 | b.Property("ContactPhone") 97 | .IsRequired() 98 | .HasMaxLength(255); 99 | 100 | b.Property("IsRegistered"); 101 | 102 | b.Property("LastUpdate"); 103 | 104 | b.Property("ModelId"); 105 | 106 | b.HasKey("Id"); 107 | 108 | b.HasIndex("ModelId"); 109 | 110 | b.ToTable("Vehicles"); 111 | }); 112 | 113 | modelBuilder.Entity("vega.Core.Models.VehicleFeature", b => 114 | { 115 | b.Property("VehicleId"); 116 | 117 | b.Property("FeatureId"); 118 | 119 | b.HasKey("VehicleId", "FeatureId"); 120 | 121 | b.HasIndex("FeatureId"); 122 | 123 | b.ToTable("VehicleFeatures"); 124 | }); 125 | 126 | modelBuilder.Entity("vega.Core.Models.Model", b => 127 | { 128 | b.HasOne("vega.Core.Models.Make", "Make") 129 | .WithMany("Models") 130 | .HasForeignKey("MakeId") 131 | .OnDelete(DeleteBehavior.Cascade); 132 | }); 133 | 134 | modelBuilder.Entity("vega.Core.Models.Photo", b => 135 | { 136 | b.HasOne("vega.Core.Models.Vehicle") 137 | .WithMany("Photos") 138 | .HasForeignKey("VehicleId"); 139 | }); 140 | 141 | modelBuilder.Entity("vega.Core.Models.Vehicle", b => 142 | { 143 | b.HasOne("vega.Core.Models.Model", "Model") 144 | .WithMany() 145 | .HasForeignKey("ModelId") 146 | .OnDelete(DeleteBehavior.Cascade); 147 | }); 148 | 149 | modelBuilder.Entity("vega.Core.Models.VehicleFeature", b => 150 | { 151 | b.HasOne("vega.Core.Models.Feature", "Feature") 152 | .WithMany() 153 | .HasForeignKey("FeatureId") 154 | .OnDelete(DeleteBehavior.Cascade); 155 | 156 | b.HasOne("vega.Core.Models.Vehicle", "Vehicle") 157 | .WithMany("Features") 158 | .HasForeignKey("VehicleId") 159 | .OnDelete(DeleteBehavior.Cascade); 160 | }); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Migrations/20170412005215_AddPhoto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace Vega.Migrations 7 | { 8 | public partial class AddPhoto : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Photos", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | FileName = table.Column(maxLength: 255, nullable: false), 19 | VehicleId = table.Column(nullable: true) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Photos", x => x.Id); 24 | table.ForeignKey( 25 | name: "FK_Photos_Vehicles_VehicleId", 26 | column: x => x.VehicleId, 27 | principalTable: "Vehicles", 28 | principalColumn: "Id", 29 | onDelete: ReferentialAction.Restrict); 30 | }); 31 | 32 | migrationBuilder.CreateIndex( 33 | name: "IX_Photos_VehicleId", 34 | table: "Photos", 35 | column: "VehicleId"); 36 | } 37 | 38 | protected override void Down(MigrationBuilder migrationBuilder) 39 | { 40 | migrationBuilder.DropTable( 41 | name: "Photos"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Migrations/VegaDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using vega.Persistence; 7 | 8 | namespace Vega.Migrations 9 | { 10 | [DbContext(typeof(VegaDbContext))] 11 | partial class VegaDbContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder 16 | .HasAnnotation("ProductVersion", "1.1.1") 17 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 18 | 19 | modelBuilder.Entity("vega.Core.Models.Feature", b => 20 | { 21 | b.Property("Id") 22 | .ValueGeneratedOnAdd(); 23 | 24 | b.Property("Name") 25 | .IsRequired() 26 | .HasMaxLength(255); 27 | 28 | b.HasKey("Id"); 29 | 30 | b.ToTable("Features"); 31 | }); 32 | 33 | modelBuilder.Entity("vega.Core.Models.Make", b => 34 | { 35 | b.Property("Id") 36 | .ValueGeneratedOnAdd(); 37 | 38 | b.Property("Name") 39 | .IsRequired() 40 | .HasMaxLength(255); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.ToTable("Makes"); 45 | }); 46 | 47 | modelBuilder.Entity("vega.Core.Models.Model", b => 48 | { 49 | b.Property("Id") 50 | .ValueGeneratedOnAdd(); 51 | 52 | b.Property("MakeId"); 53 | 54 | b.Property("Name") 55 | .IsRequired() 56 | .HasMaxLength(255); 57 | 58 | b.HasKey("Id"); 59 | 60 | b.HasIndex("MakeId"); 61 | 62 | b.ToTable("Models"); 63 | }); 64 | 65 | modelBuilder.Entity("vega.Core.Models.Photo", b => 66 | { 67 | b.Property("Id") 68 | .ValueGeneratedOnAdd(); 69 | 70 | b.Property("FileName") 71 | .IsRequired() 72 | .HasMaxLength(255); 73 | 74 | b.Property("VehicleId"); 75 | 76 | b.HasKey("Id"); 77 | 78 | b.HasIndex("VehicleId"); 79 | 80 | b.ToTable("Photos"); 81 | }); 82 | 83 | modelBuilder.Entity("vega.Core.Models.Vehicle", b => 84 | { 85 | b.Property("Id") 86 | .ValueGeneratedOnAdd(); 87 | 88 | b.Property("ContactEmail") 89 | .HasMaxLength(255); 90 | 91 | b.Property("ContactName") 92 | .IsRequired() 93 | .HasMaxLength(255); 94 | 95 | b.Property("ContactPhone") 96 | .IsRequired() 97 | .HasMaxLength(255); 98 | 99 | b.Property("IsRegistered"); 100 | 101 | b.Property("LastUpdate"); 102 | 103 | b.Property("ModelId"); 104 | 105 | b.HasKey("Id"); 106 | 107 | b.HasIndex("ModelId"); 108 | 109 | b.ToTable("Vehicles"); 110 | }); 111 | 112 | modelBuilder.Entity("vega.Core.Models.VehicleFeature", b => 113 | { 114 | b.Property("VehicleId"); 115 | 116 | b.Property("FeatureId"); 117 | 118 | b.HasKey("VehicleId", "FeatureId"); 119 | 120 | b.HasIndex("FeatureId"); 121 | 122 | b.ToTable("VehicleFeatures"); 123 | }); 124 | 125 | modelBuilder.Entity("vega.Core.Models.Model", b => 126 | { 127 | b.HasOne("vega.Core.Models.Make", "Make") 128 | .WithMany("Models") 129 | .HasForeignKey("MakeId") 130 | .OnDelete(DeleteBehavior.Cascade); 131 | }); 132 | 133 | modelBuilder.Entity("vega.Core.Models.Photo", b => 134 | { 135 | b.HasOne("vega.Core.Models.Vehicle") 136 | .WithMany("Photos") 137 | .HasForeignKey("VehicleId"); 138 | }); 139 | 140 | modelBuilder.Entity("vega.Core.Models.Vehicle", b => 141 | { 142 | b.HasOne("vega.Core.Models.Model", "Model") 143 | .WithMany() 144 | .HasForeignKey("ModelId") 145 | .OnDelete(DeleteBehavior.Cascade); 146 | }); 147 | 148 | modelBuilder.Entity("vega.Core.Models.VehicleFeature", b => 149 | { 150 | b.HasOne("vega.Core.Models.Feature", "Feature") 151 | .WithMany() 152 | .HasForeignKey("FeatureId") 153 | .OnDelete(DeleteBehavior.Cascade); 154 | 155 | b.HasOne("vega.Core.Models.Vehicle", "Vehicle") 156 | .WithMany("Features") 157 | .HasForeignKey("VehicleId") 158 | .OnDelete(DeleteBehavior.Cascade); 159 | }); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Persistence/PhotoRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.EntityFrameworkCore; 5 | using vega.Core; 6 | using vega.Core.Models; 7 | 8 | namespace vega.Persistence 9 | { 10 | public class PhotoRepository : IPhotoRepository 11 | { 12 | private readonly VegaDbContext context; 13 | public PhotoRepository(VegaDbContext context) 14 | { 15 | this.context = context; 16 | } 17 | public async Task> GetPhotos(int vehicleId) 18 | { 19 | return await context.Photos 20 | .Where(p => p.VehicleId == vehicleId) 21 | .ToListAsync(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Persistence/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using vega.Core; 3 | 4 | namespace vega.Persistence 5 | { 6 | public class UnitOfWork : IUnitOfWork 7 | { 8 | private readonly VegaDbContext context; 9 | 10 | public UnitOfWork(VegaDbContext context) 11 | { 12 | this.context = context; 13 | } 14 | 15 | public async Task CompleteAsync() 16 | { 17 | await context.SaveChangesAsync(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Persistence/VegaDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using vega.Core.Models; 3 | 4 | namespace vega.Persistence 5 | { 6 | public class VegaDbContext : DbContext 7 | { 8 | public DbSet Vehicles { get; set; } 9 | public DbSet Makes { get; set; } 10 | public DbSet Models { get; set; } 11 | public DbSet Features { get; set; } 12 | public DbSet Photos { get; set; } 13 | 14 | public VegaDbContext(DbContextOptions options) 15 | : base(options) 16 | { 17 | } 18 | 19 | protected override void OnModelCreating(ModelBuilder modelBuilder) 20 | { 21 | modelBuilder.Entity().HasKey(vf => 22 | new { vf.VehicleId, vf.FeatureId }); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Persistence/VehicleRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | using Microsoft.EntityFrameworkCore; 7 | using vega.Core; 8 | using vega.Core.Models; 9 | using vega.Extensions; 10 | 11 | namespace vega.Persistence 12 | { 13 | public class VehicleRepository : IVehicleRepository 14 | { 15 | private readonly VegaDbContext context; 16 | 17 | public VehicleRepository(VegaDbContext context) 18 | { 19 | this.context = context; 20 | } 21 | 22 | public async Task GetVehicle(int id, bool includeRelated = true) 23 | { 24 | if (!includeRelated) 25 | return await context.Vehicles.FindAsync(id); 26 | 27 | return await context.Vehicles 28 | .Include(v => v.Features) 29 | .ThenInclude(vf => vf.Feature) 30 | .Include(v => v.Model) 31 | .ThenInclude(m => m.Make) 32 | .SingleOrDefaultAsync(v => v.Id == id); 33 | } 34 | 35 | public void Add(Vehicle vehicle) 36 | { 37 | context.Vehicles.Add(vehicle); 38 | } 39 | 40 | public void Remove(Vehicle vehicle) 41 | { 42 | context.Remove(vehicle); 43 | } 44 | 45 | public async Task> GetVehicles(VehicleQuery queryObj) 46 | { 47 | var result = new QueryResult(); 48 | 49 | var query = context.Vehicles 50 | .Include(v => v.Model) 51 | .ThenInclude(m => m.Make) 52 | .AsQueryable(); 53 | 54 | query = query.ApplyFiltering(queryObj); 55 | 56 | var columnsMap = new Dictionary>>() 57 | { 58 | ["make"] = v => v.Model.Make.Name, 59 | ["model"] = v => v.Model.Name, 60 | ["contactName"] = v => v.ContactName 61 | }; 62 | query = query.ApplyOrdering(queryObj, columnsMap); 63 | 64 | result.TotalItems = await query.CountAsync(); 65 | 66 | query = query.ApplyPaging(queryObj); 67 | 68 | result.Items = await query.ToListAsync(); 69 | 70 | return result; 71 | } 72 | 73 | } 74 | } -------------------------------------------------------------------------------- /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.Hosting; 7 | 8 | namespace Vega 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vega 2 | A sample vehicle dealer app built with Angular 2, ASP.NET Core and Entity Framework Core. This is part of my Udemy course: "Build a Real-world App with ASP.NET Core and Angular 2". 3 | 4 | # To run the project: 5 | ``` 6 | $ npm install 7 | $ dotnet restore 8 | $ dotnet user-secrets set ConnectionStrings:Default "" 9 | $ webpack --config webpack.config.vendor.js 10 | $ webpack 11 | $ dotnet ef database update 12 | $ dotnet watch run 13 | ``` 14 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.SpaServices.Webpack; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using vega.Persistence; 9 | using vega.Core; 10 | using AutoMapper; 11 | using vega.Core.Models; 12 | using vega.Controllers; 13 | 14 | namespace Vega 15 | { 16 | public class Startup 17 | { 18 | public Startup(IHostingEnvironment env) 19 | { 20 | var builder = new ConfigurationBuilder() 21 | .SetBasePath(env.ContentRootPath) 22 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 23 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); 24 | 25 | if (env.IsDevelopment()) 26 | builder = builder.AddUserSecrets(); 27 | 28 | builder = builder.AddEnvironmentVariables(); 29 | Configuration = builder.Build(); 30 | } 31 | 32 | public IConfigurationRoot Configuration { get; } 33 | 34 | // This method gets called by the runtime. Use this method to add services to the container. 35 | public void ConfigureServices(IServiceCollection services) 36 | { 37 | services.Configure(Configuration.GetSection("PhotoSettings")); 38 | 39 | services.AddScoped(); 40 | services.AddScoped(); 41 | services.AddScoped(); 42 | services.AddTransient(); 43 | services.AddTransient(); 44 | 45 | services.AddAutoMapper(); 46 | 47 | services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); 48 | 49 | services.AddAuthorization(options => { 50 | options.AddPolicy(Policies.RequireAdminRole, policy => policy.RequireClaim("https://vega.com/roles", "Admin")); 51 | }); 52 | 53 | // Add framework services. 54 | services.AddMvc(); 55 | } 56 | 57 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 58 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 59 | { 60 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 61 | loggerFactory.AddDebug(); 62 | 63 | if (env.IsDevelopment()) 64 | { 65 | app.UseDeveloperExceptionPage(); 66 | app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { 67 | HotModuleReplacement = true 68 | }); 69 | } 70 | else 71 | { 72 | app.UseExceptionHandler("/Home/Error"); 73 | } 74 | 75 | app.UseStaticFiles(); 76 | 77 | var options = new JwtBearerOptions 78 | { 79 | Audience = "https://api.vega.com", 80 | Authority = "https://vegaproject.auth0.com/" 81 | }; 82 | app.UseJwtBearerAuthentication(options); 83 | 84 | app.UseMvc(routes => 85 | { 86 | routes.MapRoute( 87 | name: "default", 88 | template: "{controller=Home}/{action=Index}/{id?}"); 89 | 90 | routes.MapSpaFallbackRoute( 91 | name: "spa-fallback", 92 | defaults: new { controller = "Home", action = "Index" }); 93 | }); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Vega.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | vega-project 9 | netcoreapp1.1 10 | true 11 | false 12 | 13 | 14 | 15 | 16 | 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 | %(DistFiles.Identity) 42 | PreserveNewest 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | Loading... 6 | 7 | 8 | @section scripts { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | -------------------------------------------------------------------------------- /Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - Vega 7 | 8 | 9 | 10 | 11 | @RenderBody() 12 | 13 | @RenderSection("scripts", required: false) 14 | 15 | 16 | -------------------------------------------------------------------------------- /Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Vega 2 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 3 | @addTagHelper "*, Microsoft.AspNetCore.SpaServices" 4 | -------------------------------------------------------------------------------- /Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "PhotoSettings": { 3 | "MaxBytes": 10485760, 4 | "AcceptedFileTypes": [".jpg", ".jpeg", ".png"] 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Debug", 10 | "System": "Information", 11 | "Microsoft": "Information" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { "version": "1.0.0-preview3-004056" } 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vega", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "@angular/common": "^2.4.5", 6 | "@angular/compiler": "^2.4.5", 7 | "@angular/core": "^2.4.5", 8 | "@angular/forms": "^2.4.5", 9 | "@angular/http": "^2.4.5", 10 | "@angular/platform-browser": "^2.4.5", 11 | "@angular/platform-browser-dynamic": "^2.4.5", 12 | "@angular/platform-server": "^2.4.5", 13 | "@angular/router": "^3.4.5", 14 | "@types/node": "^6.0.42", 15 | "angular2-chartjs": "^0.2.0", 16 | "angular2-jwt": "^0.2.2", 17 | "angular2-platform-node": "~2.0.11", 18 | "angular2-template-loader": "^0.6.2", 19 | "angular2-universal": "^2.1.0-rc.1", 20 | "angular2-universal-patch": "^0.2.1", 21 | "angular2-universal-polyfills": "^2.1.0-rc.1", 22 | "aspnet-prerendering": "^2.0.0", 23 | "aspnet-webpack": "^1.0.17", 24 | "auth0-lock": "^10.15.0", 25 | "awesome-typescript-loader": "^3.0.0", 26 | "bootstrap": "^3.3.7", 27 | "chart.js": "^2.5.0", 28 | "css": "^2.2.1", 29 | "css-loader": "^0.25.0", 30 | "es6-shim": "^0.35.1", 31 | "event-source-polyfill": "^0.0.7", 32 | "expose-loader": "^0.7.1", 33 | "extract-text-webpack-plugin": "^2.0.0-rc", 34 | "file-loader": "^0.9.0", 35 | "font-awesome": "^4.7.0", 36 | "html-loader": "^0.4.4", 37 | "isomorphic-fetch": "^2.2.1", 38 | "jquery": "^2.2.1", 39 | "json-loader": "^0.5.4", 40 | "ng2-toasty": "^2.5.0", 41 | "preboot": "^4.5.2", 42 | "raven-js": "^3.14.0", 43 | "raw-loader": "^0.5.1", 44 | "rxjs": "^5.3.0", 45 | "style-loader": "^0.13.1", 46 | "to-string-loader": "^1.1.5", 47 | "typescript": "^2.2.1", 48 | "underscore": "^1.8.3", 49 | "url-loader": "^0.5.7", 50 | "webpack": "^2.2.0", 51 | "webpack-hot-middleware": "^2.12.2", 52 | "webpack-merge": "^0.14.1", 53 | "zone.js": "^0.7.8" 54 | }, 55 | "devDependencies": { 56 | "@angular/cli": "^1.0.0-rc.4", 57 | "webpack": "^2.6.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "sourceMap": true, 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "skipDefaultLibCheck": true, 9 | "lib": [ "es6", "dom" ], 10 | "types": [ "node" ] 11 | }, 12 | "exclude": [ "bin", "node_modules" ], 13 | "atom": { "rewriteTsconfig": false } 14 | } 15 | -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; 5 | 6 | module.exports = (env) => { 7 | // Configuration in common to both client-side and server-side bundles 8 | const isDevBuild = !(env && env.prod); 9 | const sharedConfig = { 10 | stats: { modules: false }, 11 | context: __dirname, 12 | resolve: { extensions: [ '.js', '.ts' ] }, 13 | output: { 14 | filename: '[name].js', 15 | publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix 16 | }, 17 | module: { 18 | rules: [ 19 | { test: /\.ts$/, include: /ClientApp/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader'] }, 20 | { test: /\.html$/, use: 'html-loader?minimize=false' }, 21 | { test: /\.css$/, use: ['to-string-loader', 'css-loader'] }, 22 | { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' } 23 | ] 24 | }, 25 | plugins: [new CheckerPlugin()] 26 | }; 27 | 28 | // Configuration for client-side bundle suitable for running in browsers 29 | const clientBundleOutputDir = './wwwroot/dist'; 30 | const clientBundleConfig = merge(sharedConfig, { 31 | entry: { 'main-client': './ClientApp/boot-client.ts' }, 32 | output: { path: path.join(__dirname, clientBundleOutputDir) }, 33 | plugins: [ 34 | new webpack.DllReferencePlugin({ 35 | context: __dirname, 36 | manifest: require('./wwwroot/dist/vendor-manifest.json') 37 | }) 38 | ].concat(isDevBuild ? [ 39 | // Plugins that apply in development builds only 40 | new webpack.SourceMapDevToolPlugin({ 41 | filename: '[file].map', // Remove this line if you prefer inline source maps 42 | moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk 43 | }) 44 | ] : [ 45 | // Plugins that apply in production builds only 46 | new webpack.optimize.UglifyJsPlugin() 47 | ]) 48 | }); 49 | 50 | // Configuration for server-side (prerendering) bundle suitable for running in Node 51 | const serverBundleConfig = merge(sharedConfig, { 52 | resolve: { mainFields: ['main'] }, 53 | entry: { 'main-server': './ClientApp/boot-server.ts' }, 54 | plugins: [ 55 | new webpack.DllReferencePlugin({ 56 | context: __dirname, 57 | manifest: require('./ClientApp/dist/vendor-manifest.json'), 58 | sourceType: 'commonjs2', 59 | name: './vendor' 60 | }) 61 | ], 62 | output: { 63 | libraryTarget: 'commonjs', 64 | path: path.join(__dirname, './ClientApp/dist') 65 | }, 66 | target: 'node', 67 | devtool: 'inline-source-map' 68 | }); 69 | 70 | return [clientBundleConfig, serverBundleConfig]; 71 | }; 72 | -------------------------------------------------------------------------------- /webpack.config.vendor.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | 6 | module.exports = (env) => { 7 | const extractCSS = new ExtractTextPlugin('vendor.css'); 8 | const isDevBuild = !(env && env.prod); 9 | const sharedConfig = { 10 | stats: { modules: false }, 11 | resolve: { extensions: [ '.js' ] }, 12 | module: { 13 | rules: [ 14 | { test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' } 15 | ] 16 | }, 17 | entry: { 18 | vendor: [ 19 | '@angular/common', 20 | '@angular/compiler', 21 | '@angular/core', 22 | '@angular/http', 23 | '@angular/platform-browser', 24 | '@angular/platform-browser-dynamic', 25 | '@angular/router', 26 | '@angular/platform-server', 27 | 'angular2-chartjs', 28 | 'angular2-jwt', 29 | 'angular2-universal', 30 | 'angular2-universal-polyfills', 31 | 'auth0-lock', 32 | 'bootstrap', 33 | 'bootstrap/dist/css/bootstrap.css', 34 | 'chart.js', 35 | 'es6-shim', 36 | 'es6-promise', 37 | 'event-source-polyfill', 38 | 'font-awesome/css/font-awesome.css', 39 | 'ng2-toasty', 40 | 'ng2-toasty/bundles/style-bootstrap.css', 41 | 'jquery', 42 | 'raven-js', 43 | 'underscore', 44 | 'zone.js', 45 | ] 46 | }, 47 | output: { 48 | publicPath: '/dist/', 49 | filename: '[name].js', 50 | library: '[name]_[hash]' 51 | }, 52 | plugins: [ 53 | new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable) 54 | new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/11580 55 | new webpack.IgnorePlugin(/^vertx$/) // Workaround for https://github.com/stefanpenner/es6-promise/issues/100 56 | ] 57 | }; 58 | 59 | const clientBundleConfig = merge(sharedConfig, { 60 | output: { path: path.join(__dirname, 'wwwroot', 'dist') }, 61 | module: { 62 | rules: [ 63 | { test: /\.css(\?|$)/, use: extractCSS.extract({ use: 'css-loader' }) } 64 | ] 65 | }, 66 | plugins: [ 67 | extractCSS, 68 | new webpack.DllPlugin({ 69 | path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'), 70 | name: '[name]_[hash]' 71 | }) 72 | ].concat(isDevBuild ? [] : [ 73 | new webpack.optimize.UglifyJsPlugin() 74 | ]) 75 | }); 76 | 77 | const serverBundleConfig = merge(sharedConfig, { 78 | target: 'node', 79 | resolve: { mainFields: ['main'] }, 80 | output: { 81 | path: path.join(__dirname, 'ClientApp', 'dist'), 82 | libraryTarget: 'commonjs2', 83 | }, 84 | module: { 85 | rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', 'css-loader'] } ] 86 | }, 87 | entry: { vendor: ['aspnet-prerendering'] }, 88 | plugins: [ 89 | new webpack.DllPlugin({ 90 | path: path.join(__dirname, 'ClientApp', 'dist', '[name]-manifest.json'), 91 | name: '[name]_[hash]' 92 | }) 93 | ] 94 | }); 95 | 96 | return [clientBundleConfig, serverBundleConfig]; 97 | } 98 | -------------------------------------------------------------------------------- /wwwroot/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mosh-hamedani/vega/4a09f50a4181d60134a9a816370b52276381c4d9/wwwroot/.DS_Store -------------------------------------------------------------------------------- /wwwroot/favicon.ico: -------------------------------------------------------------------------------- 1 |  hF ��00 �%V@@ (B�:(  @��у��̅��˅��˅��ʅ��ʅ��Ʌ�������������������������������۶�����������������������������������������������������������������������۳u�НL�Ыn�����������������������������������������������������ܵx�ҢV�Щm�����������������������������������������������������ܶ{�ӣX�Ъn�����������������������������������������������������޸�ե\�Ѭq�����������������������������������������������������຃�רa�Ӯu�����������������������������������������������������Ệ�ةd�ԯx������������������������������������������������S����⽉�ګi�հ{������������������������������������������������E����㾌�ܯq�ص�������������������������������������������������E����侎�ܭo�ײ�������������������������������������������������E����俏�ݮq�س�������������������������������������������������E����忑�ޯu�ܻ�������������������������������������������������C�����ϭ������ġ�������������������������������������������ҟ�����������������������������������������������������������������������������A����������������������������������ן�����������������������������������������( @ � ((()������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ʠ��ˡ��ʞ��ɞ��л����������������������������������������������������������������������������������������������������������ˢ�ΘC�ϛG�͙A�͙B�ϴ�����������������������������������������������������������������������������������������������������������̥�ЛJ�џP�ӤX�ӤX�Ѻ�����������������������������������������������������������������������������������������������������������ͥ�ϛI�Ԧ\�ўN�НK�ж�����������������������������������������������������������������������������������������������������������Χ�ѝL�է^�ўN�МL�ж�����������������������������������������������������������������������������������������������������������Ϩ�ўN�ը`�џP�НM�з�����������������������������������������������������������������������������������������������������������ϩ�ѝM�֨`�џP�НM�ж�����������������������������������������������������������������������������������������������������������ѫ�ӡU�׫f�ӢV�ҠS�Ѹ�����������������������������������������������������������������������������������������������������������Ѭ�ҟQ�שc�ӠS�ҟP�Ѹ�����������������������������������������������������������������������������������������������������������Ү�գY�٭j�եZ�ԣX�Һ�����������������������������������������������������������������������������������������������������������Ү�բW�٬i�դY�ԢW�ҹ�����������������������������������������������������������������������������������������������������������Ӱ�֤\�ڮm�צ^�դ[�һ����������������������������������������������������������������������������������������������...#���������ӱ�֣[�ڭk�֤\�գY�Ӻ�������������������������������������������������������������������������������������������������������ճ�٧b�ܰr�٧c�ئ`�Ի�������������������������������������������������������������������������������������������������������Բ�פ^�ۮo�إ`�פ]�Ӻ�������������������������������������������������������������������������������������������������������յ�ڨe�޲w�޴y�޲w��Ĩ������������������������������������������������������������������������������������������������������Դ�٦a�ݯr�٦a�إ_�Ժ�������������������������������������������������������������������������������������������������������ն�۩g�޲w�۪i�کg�ս�������������������������������������������������������������������������������������������������������յ�ڧd�ޱu�کf�ڧd�ռ�������������������������������������������������������������������������������������������������������շ�ۨg�޲w�۪j�۩g�ս�������������������������������������������������������������������������������������������������������ַ�۩i�߲y�ܫk�۪i�ս��������������������������������������������������������������������������������������������‡���������շ�ۨi�߲y�ݬn�ܫm��¥�������������������������������������������������������������������������������������������Ç���������ָ�ܪl�ޮr�߲y���~��˵��������������������������������������������������������������������������������������������������������ַ�ܨi�ݫl�ܩi�ܩj�ս����������������������������������������������������������������������������������������ۿ�������������������շ��ָ��ַ��ָ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ܿ���������������������(0` �%��� ihhi��������������������������������~��~��~��~}��~}���������������~��~��~��~��~��~��~�JIH_��ԁ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������zwv����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������۵w�۳u�۴u�۴v�ڳs�ڲs�ڳu��̼�����������������������������������������������������������������������������������������������������������������������������������������������������������������ϛH�ΘC�ΙD�ϛF�͘@�͘@�ΚE��­�����������������������������������������������������������������������������������������������������������������������������������������������������������������ЛI�ΙD�ΚE�ΚF�͗@�͗@�ΙD��¬�����������������������������������������������������������������������������������������������������������������������������������������������������������������џP�МL�ўO�֩c�֪c�ժc�֫e��Ƶ�����������������������������������������������������������������������������������������������������������������������������������������������������������������ѝM�ϛI�џP�׫f�ўN�ОL�џP��ï�����������������������������������������������������������������������������������������������������������������������������������������������������������������ѝL�ϛH�ўO�ըa�ϚG�ΙE�ϜJ��®�����������������������������������������������������������������������������������������������������������������������������������������������������������������ҠQ�ўN�ҢT�׫f�ўM�НL�ўO��ï�����������������������������������������������������������������������������������������������������������������������������������������������������������������ҠS�ўO�ҢU�׬g�ўN�НL�џP��ï�����������������������������������������������������������������������������������������������������������������������������������������������������������������ҟP�ѝL�ҡR�֪d�НK�ϛI�ѝN��ï�����������������������������������������������������������������������������������������������������������������������������������������������������������������ҠR�ѝM�ӢU�׫f�ОM�НK�ўO��ï�����������������������������������������������������������������������������������������������������������������������������������������������������������������ԤZ�ӢW�զ]�ٯm�ӡU�ҠS�ӢW��ı�����������������������������������������������������������������������������������������������������������������������������������������������������������������ӢV�ҟQ�ӣW�جh�ўP�ўN�ҟQ��ð���������������������������������������������������������������������������������������������������������������������������������������������{zz�����������������ԢV�ҟQ�ԢX�٬i�ҟP�ўO�ҠR��İ���������������������������������������������������������������������������������������������������������������������������������������������~}�����������������֥^�դZ�֨`�ڱq�դY�ԣX�ԤZ��Ų�����������������������������������������������������������������������������������������������������������������������������������������������������������������֣Z�աV�֥\�گm�ӢU�ӠT�ԣW��ı�����������������������������������������������������������������������������������������������������������������������������������������������������������������֤\�բX�צ^�ڰp�ԣX�ԢV�գZ��IJ�����������������������������������������������������������������������������������������������������������������������������������������������������������������اa�֥]�بd�ܲt�ץ]�դ[�է^��ų�����������������������������������������������������������������������������������������������������������������������������������������999K$$$)����������������ץ^�֢Y�צ_�ۯp�բX�ԡV�դZ��IJ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ץ_�֣[�ק`�ܰq�գY�բW�֤[��Ų����������������������������������������������������������������������������������������������������������������������������������������� ����������������کf�٨c�ګi�޳x�ئa�ئ`�بc��ƴ����������������������������������������������������������������������������������������������������������������������������������������� ����������������٧b�إ_�٩e�ݱu�פ^�פ\�צ`��ų����������������������������������������������������������������������������������������������������������������������������������������� ����������������٧b�פ^�بe�ݱu�ף]�֣\�פ^��IJ����������������������������������������������������������������������������������������������������������������������������������������� ����������������کg�٧c�۫i�ก�ܮp�ܭn�޳y��ʼ����������������������������������������������������������������������������������������������������������������������������������������� ����������������کh�ڨe�۫k�ข�ܯq�ۮp�ܯr��Ǹ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ڨe�٦b�کh�޲w�٥`�ؤ_�٦b��Ŵ����������������������������������������������������������������������������������������������������������������������������������������� ����������������۩h�ڨe�۫k�ߴz�ڧd�٧c�کf��Ƶ����������������������������������������������������������������������������������������������������������������������������������������� ����������������۪k�۩h�ܭn���~�کh�کf�۫i��ƶ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ڨh�ڧd�۫k�ߴz�ڧd�٦c�کf��Ƶ����������������������������������������������������������������������������������������������������������������������������������������� ����������������۩j�ۨf�۫l�ߵ|�ڨf�ڧd�۪h��Ƶ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ܬn�ܪk�ݮq�ᶀ�۪k�۪j�ܬl��Ƕ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ܪk�ۨh�ܬn���}�۩g�ڨf�۪j��ƶ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ܪl�ۨh�ܬn���~�ܩi�ۨh�ܬn��Ƿ����������������������������������������������������������������������������������������������������������������������������������������� ����������������ݬp�ܫm�ޮr�⺇��������ƛ����������������������������������������������������������������������������������������������������������������������������������������������������������������ݫm�ܩj�ܪl�ݮr�ݬn�ܭo�����ʽ���������������������������������������������������������������������������������������������������������������������������������������������������������������ܪl�ܨi�ܪj�ݫm�ܨi�ۨi�ܪk��ƶ��������������������������������������������������������������������������������������������������������������������������������������������������������������ݫo�ܪl�ݫm�ݬp�ܪl�ܪl�ݬo��Ƿ������������������������������������������������������������������������������������������������������������������������������������!����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!��������������������������������������������������������Ͽ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������п������������������������������������������������������������������������������������������������������߿���!��������������������������������������������������������������ֿ�������������������������������������������������������������������������������������������������������!������������������������������������������������������������������������������������������������������������������������������(@� B������ +>>>u===w===u===u===u===u===u===u===u<<>>q>>>q>>>q>>>q>>>q>>>q===q===q===q===q===q===q===q===q???s9���<<>>��������������������ОM�ΘB�ΘC�ΘA�НK�ΙB�͘?�͗>�ΛF�ΙD�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ўN�ϙE�ϚE�ΙD�ОL�ΚD�ΙB�͘@�ϛG�ΚE�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������џN�ϙD�ϙE�ΙD�ϜJ�͗A�͖>�̕=�ΙD�͘A�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ҡV�џP�ўO�МK�حj�ڲr�ٱo�ٰn�ٳs�ڲp�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ҠR�МJ�ЛJ�ΙD�۵x�ԤY�џN�џM�ҢT�ҠR�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ҠQ�ЛH�ϛH�ΘC�ڲs�џO�ΘD�ΘC�МJ�ϛH�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ҠR�ЛH�МI�ΘD�ڲs�ѠP�ϙE�ΘE�НL�ϜJ�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ԣW�ҟP�ҟP�НK�ܶy�ӣW�ўM�ўM�ҠR�џP�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������>>>��������������������ԣX�ҟQ�ҟQ�НL�ܶy�ӤX�ўM�ОM�ҠR�џQ�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������ӡU�ѝK�ѝL�ЛF�۴u�ҡS�ЛH�ϛG�ўN�ѝL�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������ԣW�ўM�ѝM�МH�۴v�ӢU�НJ�ЛI�џP�ѝN�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������ԣW�ўN�ҞN�ќJ�ܴx�ӢV�НK�НJ�ҟQ�ўO�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������֧^�գY�ԤZ�ӡU�޹��֨_�ӡU�ӡT�ԤZ�ԣX�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������դZ�ҟQ�ҟQ�ѝK�ܶy�ԢW�ўM�ѝL�ҠR�ҟP�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������ե[�ӠR�ҠR�ўN�޶z�գY�ѝO�ўN�ӡS�ҠR�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������???��������������������֥[�ԟQ�ҠR�ќM�޶z�դY�ҞN�ѝN�ӡS�ӡQ�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������@@@��������������������רb�֥\�֥\�ԣW�߻��שb�գX�ԣX�զ]�եZ�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������@@@��������������������צ_�բW�բW�ԠR�޹�ק^�ԡS�ԠS�ԣY�գW�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������BBB��������������������צ^�աU�աU�ӟP�޸~�֦\�ҠR�ӠR�գX�ԣV�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������BBB��������������������اa�֣Z�գZ�աU�߹��רa�բW�բV�֥\�֣Z�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������BBB��������������������ڪf�ק`�צ`�֤\�༆�٫g�צ]�֥]�֨a�֨`�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������999a>>>]'��������������������اa�֢X�עX�՟T�߹��ا`�ՠU�ԠT�֤[�դY����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������بd�֣[�֣Z�աV�แ�اa�բW�բV�֥\�֤[����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������٨d�ף[�֣\�աV�ṁ�קa�բW�բV�֥\�֤[����������������������������������������������������������������������������������������������������������������������������������������������Ŀ����������������������������������������� �����������������������۬j�ڨd�کe�ئ`�⽉�۬j�ئ`�ئ`�٩e�٩c����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۪g�٦`�٦`�ؤ\�ễ�کg�פ\�פ]�٧a�צ`����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ڪf�إ^�פ^�֢Z�ễ�٨e�ף[�֣Z�ئa�ץ^����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۪g�إ_�ؤ_�֢[�⻅�٨e�ע[�֢[�٦a�֣Z����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۫i�٦b�٧b�ؤ]�㽉�ܯp�ڨf�کf�۫j�฀����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ܭn�۫i�۪i�ڧc��“�⾌�ễ�ẃ�⼉�ṃ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۪i�٥a�٦a�أ]�⺆�ڨe�עZ�֢Z�إ`�פ^����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۫j�ڦb�ڧc�ؤ^�⼈�۫i�٥_�٤_�کe�٦c����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������۫k�ڦc�ڧc�٤_�㼈�۫i�٤`�ئ`�ڨf�٨d����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ݮq�ܫl�ܬl�۪h�����ݰs�۫j�۫i�ܭn�ܭl����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ܫl�ڧd�ڧe�٥`�㽊�ܬk�٦b�٦b�۩g�کf����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ܫm�ڧe�ڧe�٥a�㽊�ܬl�ڦb�٦b�۩h�کf����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ܫm�ۧe�ۨe�٥a�㽊�ܬl�ڦc�ڦb�۪h�۩f����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ޭq�ܪj�ܪk�ۨf�俎�ݮq�۩h�۩h�ܬm�ܫk����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ݮr�ܫl�ܫl�۩h�忐�ޯs�۪j�۪j�ܭn�ܭm����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ݬn�ۧf�ۨg�ڦc�佋�ݬn�ڧd�ڧd�۪j�۪i����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ݬo�ۨh�ۨh�ڦd�佌�ݬn�ۧe�ۧe�۩i�ڧd����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������ݭq�ܩj�ܪj�ۧf�����߲w�ݭo�ݫm���{�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������߯u�ެp�ݭp�ݫm�㻈�忐�位�㼉��ɡ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ݭp�ܨh�ܩh�ܨh�ܫn�ۧf�ۦd�ڦc�ܪk�ްt����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ݭp�ܨi�ܩj�ܩi�ޭq�ܪk�ܨi�ۨh�ݫm�۪j���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ݭp�ܧh�ܨi�ܩh�ޭq�ܪj�ܨh�ۨg�ݬm�ܫl��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ޯt�ݬp�ݭp�ݭp�߯u�ݭq�ޭp�ݬo�ޮs�ޮr�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� --------------------------------------------------------------------------------