├── .github ├── Android.png ├── cordova.png ├── distFolder.png ├── electronnd.png ├── webnd.png ├── webpack-server.png ├── win10nd.png ├── win81nd.png └── winMobile.png ├── .gitignore ├── Client ├── .vscode │ └── settings.json ├── assets │ ├── cordova │ │ └── config.xml │ ├── electron │ │ ├── index.js │ │ └── package.json │ └── toggleHamburger.js ├── config │ └── lite-prod-config.json ├── img │ ├── background.jpg │ ├── background_bak.jpg │ └── windows │ │ ├── Square150x150Logo.png │ │ ├── Square310x310Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square70x70Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Wide310x150Logo.png │ │ ├── high.png │ │ ├── smalllogo.png │ │ ├── storelogo.png │ │ └── wide.png ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── about │ │ │ ├── about.module.ts │ │ │ ├── about.routes.js │ │ │ ├── about.routes.js.map │ │ │ ├── about.routes.ts │ │ │ └── components │ │ │ │ └── about │ │ │ │ ├── about.component.html │ │ │ │ └── about.component.ts │ │ ├── account │ │ │ ├── account.module.ts │ │ │ ├── account.routes.js │ │ │ ├── account.routes.js.map │ │ │ ├── account.routes.ts │ │ │ └── components │ │ │ │ ├── login │ │ │ │ ├── login.component.html │ │ │ │ └── login.component.ts │ │ │ │ └── register │ │ │ │ ├── register.component.html │ │ │ │ └── register.component.ts │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routes.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ ├── factories │ │ │ │ └── cameraFactory.ts │ │ │ └── services │ │ │ │ ├── authentication.service.ts │ │ │ │ ├── camera.service.js │ │ │ │ ├── camera.service.js.map │ │ │ │ ├── camera.service.ts │ │ │ │ ├── currentUser.service.ts │ │ │ │ ├── desktopCamera.service.js │ │ │ │ ├── desktopCamera.service.ts │ │ │ │ ├── food-data.service.ts │ │ │ │ ├── foodList-data.service.ts │ │ │ │ ├── httpWrapper.service.ts │ │ │ │ ├── mobileCamera.service.js │ │ │ │ ├── mobileCamera.service.js.map │ │ │ │ ├── mobileCamera.service.ts │ │ │ │ ├── platformInformation.service.ts │ │ │ │ └── storage.service.ts │ │ ├── food │ │ │ ├── components │ │ │ │ ├── food │ │ │ │ │ ├── food.component.html │ │ │ │ │ └── food.component.ts │ │ │ │ ├── foodListDetails │ │ │ │ │ ├── foodListDetails.component.html │ │ │ │ │ └── foodListDetails.component.ts │ │ │ │ ├── foodListForm │ │ │ │ │ ├── foodListForm.component.html │ │ │ │ │ └── foodListForm.component.ts │ │ │ │ └── foodlists │ │ │ │ │ ├── foodlists.component.html │ │ │ │ │ └── foodlists.component.ts │ │ │ ├── food.module.ts │ │ │ ├── food.routes.js │ │ │ ├── food.routes.js.map │ │ │ └── food.routes.ts │ │ ├── home │ │ │ ├── components │ │ │ │ └── home │ │ │ │ │ ├── home.component.html │ │ │ │ │ └── home.component.ts │ │ │ ├── home.module.ts │ │ │ ├── home.routes.js │ │ │ ├── home.routes.js.map │ │ │ └── home.routes.ts │ │ ├── layout │ │ │ ├── components │ │ │ │ └── header │ │ │ │ │ ├── header.component.html │ │ │ │ │ └── header.component.ts │ │ │ └── layout.module.ts │ │ ├── main-aot.js.map │ │ ├── main.js.map │ │ ├── polyfills.js.map │ │ ├── shared │ │ │ ├── app.constants.js │ │ │ ├── app.constants.js.map │ │ │ ├── app.constants.ts │ │ │ ├── decorators │ │ │ │ └── needsAuthentication.ts │ │ │ ├── models │ │ │ │ ├── foodItem.js │ │ │ │ ├── foodItem.js.map │ │ │ │ ├── foodItem.ts │ │ │ │ ├── foodList.js │ │ │ │ ├── foodList.js.map │ │ │ │ ├── foodList.ts │ │ │ │ ├── loginUser.js │ │ │ │ ├── loginUser.js.map │ │ │ │ ├── loginUser.ts │ │ │ │ ├── token.js │ │ │ │ ├── token.js.map │ │ │ │ └── token.ts │ │ │ └── shared.module.ts │ │ └── vendor.js.map │ ├── css │ │ └── custom.css │ ├── favicon.ico │ ├── index.html │ ├── main-aot.ts │ ├── main.js │ ├── main.js.map │ ├── main.ts │ ├── polyfills.js │ ├── polyfills.js.map │ ├── polyfills.ts │ ├── vendor.js │ ├── vendor.js.map │ └── vendor.ts ├── tsconfig-aot.json ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── webpack.dev.js ├── webpack.prod.js └── yarn.lock ├── README.MD └── Server ├── FoodChooser.sln └── FoodChooser ├── Configuration └── AppSettings.cs ├── Controllers ├── AccountController.cs ├── FoodListsController.cs └── FoodsController.cs ├── Dtos ├── FoodItemDto.cs ├── FoodListDto.cs ├── LinkDto.cs └── SharedFoodListDto.cs ├── FoodChooser.csproj ├── FoodChooser.csproj.user ├── Helpers ├── DynamicExtensions.cs └── QueryParametersExtensions.cs ├── IdentityConfig.cs ├── Migrations ├── 20171025161811_InitialCreate.Designer.cs ├── 20171025161811_InitialCreate.cs └── FoodChooserDbContextModelSnapshot.cs ├── Models ├── FoodItem.cs ├── FoodList.cs ├── QueryParameters.cs └── RegisterBindingModel.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Repositories ├── Food │ ├── FoodRepository.cs │ └── IFoodRepository.cs ├── FoodChooserDbContext.cs └── List │ ├── FoodListRepository.cs │ └── IFoodListRepository.cs ├── Services ├── DataBaseInit │ ├── DatabaseInitializer.cs │ └── IDatabaseInitializer.cs └── RandomNumber │ ├── IRandomNumberGenerator.cs │ └── RandomNumberGenerator.cs ├── Startup.cs ├── appsettings.development.json ├── appsettings.json ├── tempkey.rsa └── wwwroot └── FoodImages ├── 34f6c25a-8045-4c69-b4ab-cff2c8466758.png ├── 7f7c8988-2750-4bf6-9595-805880e10452.png ├── 8267a578-bd32-45ab-ad7e-0e44d7e67786.png ├── b2ba66c1-8914-42ca-84bf-924aad99da2f.png └── dummy.png /.github/Android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/Android.png -------------------------------------------------------------------------------- /.github/cordova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/cordova.png -------------------------------------------------------------------------------- /.github/distFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/distFolder.png -------------------------------------------------------------------------------- /.github/electronnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/electronnd.png -------------------------------------------------------------------------------- /.github/webnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/webnd.png -------------------------------------------------------------------------------- /.github/webpack-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/webpack-server.png -------------------------------------------------------------------------------- /.github/win10nd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/win10nd.png -------------------------------------------------------------------------------- /.github/win81nd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/win81nd.png -------------------------------------------------------------------------------- /.github/winMobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/.github/winMobile.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Client/node_modules 2 | Server/.vs/ 3 | Client/app/*.js 4 | Client/app/*.js.map 5 | Client/app/components/**/*.js 6 | Client/app/components/**/*.js.map 7 | Server/FoodChooser/bin 8 | Server/FoodChooser/obj 9 | Server/packages/ 10 | Server/FoodChooser/Properties/PublishProfiles/ 11 | Client/typings 12 | .dist 13 | .temp 14 | Client/app/shared/services/*.js 15 | Client/app/shared/services/*.js.map 16 | Client/app/models/*.js.map 17 | Client/app/models/*.js 18 | Client/app/shared/app.constants.js 19 | Client/app/shared/app.constants.js.map 20 | Client/app/decorators/needsAuthentication.js 21 | Client/app/decorators/needsAuthentication.js.map 22 | Client/npm-debug.log 23 | Client/aot/ 24 | Client/src/.aot 25 | Client/src/app/**/*.js 26 | Client/src/app/**/*.js.map 27 | 28 | /Server/FoodChooser/wwwroot/FoodImages/*.png 29 | -------------------------------------------------------------------------------- /Client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.check.workspaceVersion": false, 3 | "vsicons.presets.angular": true 4 | } -------------------------------------------------------------------------------- /Client/assets/cordova/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FoodChooser A2 4 | 5 | A small demo app to give yoou FoodSuggestions 6 | 7 | 8 | Offering.Solutions 9 | 10 | 11 | 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 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Client/assets/electron/index.js: -------------------------------------------------------------------------------- 1 | // main/index.js 2 | 3 | 'use strict'; 4 | const electron = require('electron'), 5 | app = electron.app, 6 | BrowserWindow = electron.BrowserWindow; 7 | 8 | var mainWindow = null; 9 | 10 | app.on('window-all-closed', function() { 11 | if (process.platform != 'darwin') { 12 | app.quit(); 13 | } 14 | }); 15 | 16 | app.on('ready', function() { 17 | mainWindow = new BrowserWindow({ 18 | width: 1024, 19 | height: 768, 20 | nodeIntegration: false 21 | }); 22 | mainWindow.loadURL('file://' + __dirname + '/index.html'); 23 | //mainWindow.webContents.openDevTools(); 24 | mainWindow.on('closed', function() { 25 | mainWindow = null; 26 | }); 27 | }); -------------------------------------------------------------------------------- /Client/assets/electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main" : "index.js", 3 | "version" : "0.0.1", 4 | "name": "FoodChooser" 5 | } -------------------------------------------------------------------------------- /Client/assets/toggleHamburger.js: -------------------------------------------------------------------------------- 1 | // Make Hamburgermenu dissappear after cliking on it on mobile devices 2 | $(document).ready(function () { 3 | $(document).on('click', '.navbar-collapse.in', function (e) { 4 | if ($(e.target).is('a') && $(e.target).attr('class') != 'dropdown-toggle') { 5 | $(this).collapse('hide'); 6 | } 7 | }); 8 | }); -------------------------------------------------------------------------------- /Client/config/lite-prod-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "baseDir": "./.dist/web/aot/" 4 | } 5 | } -------------------------------------------------------------------------------- /Client/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/background.jpg -------------------------------------------------------------------------------- /Client/img/background_bak.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/background_bak.jpg -------------------------------------------------------------------------------- /Client/img/windows/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Square150x150Logo.png -------------------------------------------------------------------------------- /Client/img/windows/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Square310x310Logo.png -------------------------------------------------------------------------------- /Client/img/windows/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Square44x44Logo.png -------------------------------------------------------------------------------- /Client/img/windows/Square70x70Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Square70x70Logo.png -------------------------------------------------------------------------------- /Client/img/windows/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Square71x71Logo.png -------------------------------------------------------------------------------- /Client/img/windows/Wide310x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/Wide310x150Logo.png -------------------------------------------------------------------------------- /Client/img/windows/high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/high.png -------------------------------------------------------------------------------- /Client/img/windows/smalllogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/smalllogo.png -------------------------------------------------------------------------------- /Client/img/windows/storelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/storelogo.png -------------------------------------------------------------------------------- /Client/img/windows/wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/img/windows/wide.png -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foodchooserangular2", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "tsc && webpack-dev-server --env=dev --hot --open", 6 | "lite": "lite-server --config=config/lite-prod-config.json", 7 | "tsc": "tsc", 8 | "tsc:w": "tsc -w", 9 | "test": "karma start karma.conf.js", 10 | "lint": "tslint ./app/**/*.ts -t verbose", 11 | "build-dev": "webpack --env=dev", 12 | "build-prod": "webpack --env=prod", 13 | "build-web": "npm run build-prod", 14 | "prepare-desktop": "mkdirp ./.temp/electron && ncp assets/electron .temp/electron/ && ncp .dist/web/aot .temp/electron/ && rimraf .dist/desktop/", 15 | "build-desktop": "npm run prepare-desktop && electron-packager .temp/electron/ --electronVersion=1.4.15 --platform=win32,linux --out=./.dist/desktop/ && npm run cleanup", 16 | "prepare-mobile": "rimraf ./.temp/mobile && mkdirp ./.temp/mobile/www && mkdirp ./.temp/mobile/www/img && ncp assets/cordova .temp/mobile/ && ncp .dist/web/aot .temp/mobile/www && ncp img .temp/mobile/www/img && rimraf .dist/mobile/ && trash .temp/mobile/www/**/*.js.gz", 17 | "build-mobile-windows": "mkdirp .dist/mobile/windows && cd .temp/mobile && cordova platform add windows && cordova build windows && cd ../.. && ncp .temp/mobile/platforms/windows .dist/mobile/windows", 18 | "build-mobile-android": "mkdirp .dist/mobile/android && cd .temp/mobile && cordova platform add android && cordova build android && cd ../.. && ncp .temp/mobile/platforms/android .dist/mobile/android", 19 | "build-mobile": "npm run prepare-mobile && npm run build-mobile-windows && npm run build-mobile-android && npm run cleanup", 20 | "build-all": "rimraf .dist && npm run build-web && npm run build-desktop && npm run build-mobile && npm run cleanup", 21 | "cleanup": "rimraf .temp" 22 | }, 23 | "license": "ISC", 24 | "dependencies": { 25 | "@angular/animations": "^5.0.0", 26 | "@angular/common": "~5.0.0", 27 | "@angular/compiler": "~5.0.0", 28 | "@angular/compiler-cli": "~5.0.0", 29 | "@angular/core": "~5.0.0", 30 | "@angular/forms": "~5.0.0", 31 | "@angular/http": "~5.0.0", 32 | "@angular/platform-browser": "~5.0.0", 33 | "@angular/platform-browser-dynamic": "~5.0.0", 34 | "@angular/platform-server": "~5.0.0", 35 | "@angular/router": "~5.0.0", 36 | "@angular/upgrade": "~5.0.0", 37 | "bootstrap": "^3.3.7", 38 | "core-js": "2.5.1", 39 | "ie-shim": "~0.1.0", 40 | "jquery": "^3.2.1", 41 | "reflect-metadata": "0.1.10", 42 | "rxjs": "5.5.2", 43 | "zone.js": "0.8.18" 44 | }, 45 | "devDependencies": { 46 | "@ngtools/webpack": "^1.8.0", 47 | "@types/jasmine": "^2.6.2", 48 | "@types/node": "8.0.47", 49 | "@types/selenium-webdriver": "^3.0.8", 50 | "angular-router-loader": "^0.6.0", 51 | "angular2-template-loader": "^0.6.2", 52 | "awesome-typescript-loader": "^3.3.0", 53 | "canonical-path": "0.0.2", 54 | "clean-webpack-plugin": "^0.1.17", 55 | "compression-webpack-plugin": "^1.0.1", 56 | "concurrently": "^3.5.0", 57 | "copy-webpack-plugin": "^4.2.0", 58 | "css-loader": "^0.28.7", 59 | "electron-packager": "^9.1.0", 60 | "extract-text-webpack-plugin": "^3.0.2", 61 | "favicons-webpack-plugin": "0.0.7", 62 | "file-loader": "^1.1.5", 63 | "html-loader": "^0.5.1", 64 | "html-webpack-plugin": "^2.30.1", 65 | "json-loader": "^0.5.7", 66 | "mkdirp": "^0.5.1", 67 | "ncp": "^2.0.0", 68 | "node-sass": "^4.6.0", 69 | "null-loader": "^0.1.1", 70 | "path": "^0.12.7", 71 | "raw-loader": "^0.5.1", 72 | "rimraf": "^2.6.2", 73 | "sass-loader": "^6.0.6", 74 | "source-map-loader": "^0.2.3", 75 | "style-loader": "^0.19.0", 76 | "trash-cli": "^1.4.0", 77 | "ts-helpers": "^1.1.2", 78 | "ts-loader": "^3.1.1", 79 | "tslint": "^5.8.0", 80 | "tslint-loader": "^3.5.3", 81 | "typescript": "2.6.1", 82 | "url-loader": "^0.6.2", 83 | "webdriver-manager": "12.0.6", 84 | "webpack": "^3.8.1", 85 | "webpack-bundle-analyzer": "^2.9.0", 86 | "webpack-dev-server": "2.9.4", 87 | "webpack-stream": "^4.0.0" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Client/src/app/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AboutRoutes } from './about.routes'; 5 | import { AboutComponent } from './components/about/about.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | AboutRoutes 11 | ], 12 | 13 | declarations: [ 14 | AboutComponent 15 | ], 16 | 17 | providers: [ 18 | ] 19 | }) 20 | 21 | export class AboutModule { } 22 | -------------------------------------------------------------------------------- /Client/src/app/about/about.routes.js: -------------------------------------------------------------------------------- 1 | import { RouterModule } from '@angular/router'; 2 | import { AboutComponent } from './components/about/about.component'; 3 | var aboutRoutes = [ 4 | { path: '', component: AboutComponent } 5 | ]; 6 | export var AboutRoutes = RouterModule.forChild(aboutRoutes); 7 | //# sourceMappingURL=about.routes.js.map -------------------------------------------------------------------------------- /Client/src/app/about/about.routes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"about.routes.js","sourceRoot":"","sources":["about.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAU,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEpE,IAAM,WAAW,GAAW;IACxB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE;CAC1C,CAAC;AAEF,MAAM,CAAC,IAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/about/about.routes.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | 3 | import { AboutComponent } from './components/about/about.component'; 4 | 5 | const aboutRoutes: Routes = [ 6 | { path: '', component: AboutComponent } 7 | ]; 8 | 9 | export const AboutRoutes = RouterModule.forChild(aboutRoutes); 10 | -------------------------------------------------------------------------------- /Client/src/app/about/components/about/about.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | Small application for demo purposes.

This application helps you to get suggestions what to cook if 6 | you have no idea. You can register and login, manage your food suggestions in lists, publish and unpublish them. 7 | If public they will be randomly shown on the startpage also to unregistered users to get some inspiration. 8 |
This page is NOT about giving you recipes because the Internet if full with sites to get recipes from. This 9 | is just to get inspirations what to bring on your plates. 10 |
This application was made for demo purposes and will not send you any newsletters or use your data for 11 | anything else.

Made with:

12 | 35 | 36 |

Hosted on Azure


37 | Offering.Solutions - Fabian Gosebrink 38 |
39 |
40 | 41 | 42 |
43 | -------------------------------------------------------------------------------- /Client/src/app/about/components/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'about-component', 5 | templateUrl: './about.component.html' 6 | }) 7 | 8 | export class AboutComponent { 9 | 10 | constructor() { } 11 | } 12 | -------------------------------------------------------------------------------- /Client/src/app/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { AccountRoutes } from './account.routes'; 7 | import { LoginComponent } from './components/login/login.component'; 8 | import { RegisterComponent } from './components/register/register.component'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CommonModule, 13 | HttpClientModule, 14 | FormsModule, 15 | AccountRoutes 16 | ], 17 | 18 | declarations: [ 19 | LoginComponent, 20 | RegisterComponent 21 | ], 22 | 23 | providers: [ 24 | ] 25 | }) 26 | 27 | export class AccountModule { } 28 | -------------------------------------------------------------------------------- /Client/src/app/account/account.routes.js: -------------------------------------------------------------------------------- 1 | import { RouterModule } from '@angular/router'; 2 | import { LoginComponent } from './components/login/login.component'; 3 | import { RegisterComponent } from './components/register/register.component'; 4 | var accountRoutes = [ 5 | { path: '', redirectTo: 'login' }, 6 | { path: 'login', component: LoginComponent }, 7 | { path: 'register', component: RegisterComponent } 8 | ]; 9 | export var AccountRoutes = RouterModule.forChild(accountRoutes); 10 | //# sourceMappingURL=account.routes.js.map -------------------------------------------------------------------------------- /Client/src/app/account/account.routes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"account.routes.js","sourceRoot":"","sources":["account.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAU,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,IAAM,aAAa,GAAW;IAC1B,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE;IACjC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE;CACrD,CAAC;AAEF,MAAM,CAAC,IAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/account/account.routes.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | 3 | import { LoginComponent } from './components/login/login.component'; 4 | import { RegisterComponent } from './components/register/register.component'; 5 | 6 | const accountRoutes: Routes = [ 7 | { path: '', redirectTo: 'login' }, 8 | { path: 'login', component: LoginComponent }, 9 | { path: 'register', component: RegisterComponent } 10 | ]; 11 | 12 | export const AccountRoutes = RouterModule.forChild(accountRoutes); 13 | -------------------------------------------------------------------------------- /Client/src/app/account/components/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | Error! {{errorMessage}} 6 |
7 | 8 | 31 | 32 |
33 | 34 |
-------------------------------------------------------------------------------- /Client/src/app/account/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthenticationService } from './../../../core/services/authentication.service'; 5 | import { LoginUser } from './../../../shared/models/loginUser'; 6 | import { Token } from './../../../shared/models/token'; 7 | 8 | @Component({ 9 | selector: 'login-component', 10 | templateUrl: './login.component.html' 11 | }) 12 | 13 | export class LoginComponent { 14 | 15 | loginUser: LoginUser; 16 | errorMessage: string; 17 | 18 | constructor(private authService: AuthenticationService, private router: Router) { 19 | this.loginUser = new LoginUser(); 20 | } 21 | 22 | doLoginUser() { 23 | this.authService 24 | .loginUser(this.loginUser.Username, this.loginUser.Password) 25 | .subscribe( 26 | (response: Token) => this.router.navigate(['/home']), 27 | (error: any) => { 28 | console.log(error); 29 | this.errorMessage = JSON.parse(error._body).error_description; 30 | this.loginUser.Password = ''; 31 | }); 32 | } 33 | 34 | redirectTo(target: string, $event: any) { 35 | $event.preventDefault(); 36 | this.router.navigate([target]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Client/src/app/account/components/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Success! {{successMessage}} 5 |
6 | 7 |
8 | Error! {{errorMessage}} 9 |
10 | 11 | 29 | 30 |
31 | 32 |
-------------------------------------------------------------------------------- /Client/src/app/account/components/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthenticationService } from './../../../core/services/authentication.service'; 5 | 6 | @Component({ 7 | selector: 'register-component', 8 | templateUrl: './register.component.html' 9 | }) 10 | 11 | 12 | export class RegisterComponent { 13 | 14 | registerUser: any; 15 | errorMessage: string; 16 | successMessage: string; 17 | 18 | constructor(private authService: AuthenticationService, private router: Router) { 19 | this.registerUser = {}; 20 | } 21 | 22 | doRegisterUser() { 23 | this.errorMessage = ''; 24 | this.successMessage = ''; 25 | 26 | this.authService 27 | .registerUser( 28 | this.registerUser.Username, 29 | this.registerUser.Email, 30 | this.registerUser.Password, 31 | this.registerUser.ConfirmPassword 32 | ) 33 | .subscribe((response: any) => { 34 | this.successMessage = 'You have been registered. Please login.'; 35 | }, (error) => { 36 | this.errorMessage = 'There was an Error: '; 37 | let errorObject = error._body; 38 | let parsedErrorObject = JSON.parse(errorObject); 39 | 40 | for (let propertyName of parsedErrorObject) { 41 | this.errorMessage += parsedErrorObject[propertyName][0]; 42 | } 43 | }); 44 | } 45 | 46 | redirectTo(target: string, $event: any) { 47 | $event.preventDefault(); 48 | this.router.navigate([target]); 49 | } 50 | } -------------------------------------------------------------------------------- /Client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AuthenticationService } from './core/services/authentication.service'; 4 | 5 | @Component({ 6 | selector: 'foodChooser-app', 7 | templateUrl: './app.component.html' 8 | }) 9 | 10 | export class AppComponent { 11 | constructor(public authenticationService: AuthenticationService) { } 12 | } 13 | -------------------------------------------------------------------------------- /Client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { AppRoutes } from './app.routes'; 8 | import { CoreModule } from './core/core.module'; 9 | import { HomeModule } from './home/home.module'; 10 | import { LayoutModule } from './layout/layout.module'; 11 | import { SharedModule } from './shared/shared.module'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | BrowserModule, 16 | CoreModule.forRoot(), 17 | AppRoutes, 18 | HttpClientModule, 19 | FormsModule, 20 | HomeModule, 21 | LayoutModule, 22 | 23 | SharedModule 24 | ], 25 | 26 | declarations: [ 27 | AppComponent 28 | ], 29 | 30 | providers: [], 31 | 32 | bootstrap: [AppComponent] 33 | }) 34 | 35 | export class AppModule { } 36 | -------------------------------------------------------------------------------- /Client/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | 3 | const appRoutes: Routes = [ 4 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 5 | { path: 'about', loadChildren: './about/about.module#AboutModule' }, 6 | { path: 'account', loadChildren: './account/account.module#AccountModule' }, 7 | { path: 'foodlists', loadChildren: './food/food.module#FoodModule' }, 8 | { 9 | path: '**', 10 | redirectTo: 'home' 11 | } 12 | ]; 13 | 14 | export const AppRoutes = RouterModule.forRoot(appRoutes, { useHash: true }); 15 | -------------------------------------------------------------------------------- /Client/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 3 | import { ModuleWithProviders } from '@angular/compiler/src/core'; 4 | import { NgModule } from '@angular/core'; 5 | 6 | import { CameraFactory } from './factories/cameraFactory'; 7 | import { AuthenticationService } from './services/authentication.service'; 8 | import { CurrentUserService } from './services/currentUser.service'; 9 | import { FoodDataService } from './services/food-data.service'; 10 | import { FoodListDataService } from './services/foodList-data.service'; 11 | import { HttpWrapperService, MyFirstInterceptor } from './services/httpWrapper.service'; 12 | import { PlatformInformationService } from './services/platformInformation.service'; 13 | import { StorageService } from './services/storage.service'; 14 | 15 | @NgModule({ 16 | imports: [CommonModule, HttpClientModule], 17 | exports: [], 18 | declarations: [], 19 | providers: [ 20 | // see below 21 | ], 22 | }) 23 | 24 | export class CoreModule { 25 | static forRoot(): ModuleWithProviders { 26 | return { 27 | ngModule: CoreModule, 28 | providers: [ 29 | AuthenticationService, 30 | HttpWrapperService, 31 | CurrentUserService, 32 | StorageService, 33 | PlatformInformationService, 34 | CameraFactory, 35 | FoodDataService, 36 | FoodListDataService, 37 | { 38 | provide: HTTP_INTERCEPTORS, 39 | useClass: MyFirstInterceptor, 40 | multi: true, 41 | }] 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Client/src/app/core/factories/cameraFactory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { DesktopCameraService } from './../services/desktopCamera.service'; 4 | import { MobileCameraService } from './../services/mobileCamera.service'; 5 | import { PlatformInformationService } from './../services/platformInformation.service'; 6 | 7 | @Injectable() 8 | export class CameraFactory { 9 | 10 | constructor(private _platfornInformationService: PlatformInformationService) { 11 | 12 | } 13 | 14 | getCameraService(): any { 15 | 16 | if (this._platfornInformationService.isMobile) { 17 | return new MobileCameraService(); 18 | } 19 | 20 | return new DesktopCameraService(); 21 | }; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Client/src/app/core/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Router } from '@angular/router'; 4 | import { Observable } from 'rxjs/Rx'; 5 | 6 | import { CONFIGURATION } from './../../shared/app.constants'; 7 | import { Token } from './../../shared/models/token'; 8 | import { CurrentUserService } from './currentUser.service'; 9 | import { Observer } from 'rxjs/Observer'; 10 | 11 | @Injectable() 12 | export class AuthenticationService { 13 | 14 | constructor(private currentUserService: CurrentUserService, 15 | private http: HttpClient, 16 | private router: Router) { } 17 | 18 | get isAuthenticated(): boolean { 19 | return !!this.currentUserService.token; 20 | } 21 | 22 | loginUser(username: string, password: string): Observable { 23 | const clientId = 'client_id=' + CONFIGURATION.authConfig.CLIENT_ID; 24 | const grantType = 'grant_type=' + CONFIGURATION.authConfig.GRANT_TYPE; 25 | const usernameForBody = 'username=' + username; 26 | const passwordForBody = 'password=' + password; 27 | const scope = 'scope=' + CONFIGURATION.authConfig.SCOPE; 28 | 29 | const body = clientId.concat('&', grantType, '&', usernameForBody, '&', passwordForBody, '&', scope); 30 | 31 | const options = { 32 | headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') 33 | }; 34 | 35 | return Observable.create((observer: Observer) => { 36 | this.http.post(CONFIGURATION.baseUrls.server + 'connect/token', body, options) 37 | .subscribe((tokenData: Token) => { 38 | this.currentUserService.token = tokenData.access_token; 39 | this.currentUserService.username = username; 40 | observer.next(tokenData); 41 | }, (error) => observer.error(error), 42 | () => observer.complete()); 43 | }); 44 | } 45 | 46 | logoutUser() { 47 | this.currentUserService.token = ''; 48 | this.router.navigate(['/home']); 49 | } 50 | 51 | registerUser(username: string, email: string, password: string, confirmPassword: string): Observable { 52 | let registerData = { 53 | Email: email, 54 | Username: username, 55 | Password: password, 56 | ConfirmPassword: confirmPassword 57 | }; 58 | 59 | return this.http.post(CONFIGURATION.baseUrls.server + CONFIGURATION.baseUrls.apiUrl + 'account/register', 60 | JSON.stringify(registerData)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Client/src/app/core/services/camera.service.js: -------------------------------------------------------------------------------- 1 | //# sourceMappingURL=camera.service.js.map -------------------------------------------------------------------------------- /Client/src/app/core/services/camera.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"camera.service.js","sourceRoot":"","sources":["camera.service.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /Client/src/app/core/services/camera.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | 3 | export interface ICameraService { 4 | getPhoto(): Observable; 5 | } 6 | -------------------------------------------------------------------------------- /Client/src/app/core/services/currentUser.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { StorageService } from './storage.service'; 4 | 5 | @Injectable() 6 | export class CurrentUserService { 7 | 8 | constructor(private storageService: StorageService) { 9 | 10 | } 11 | 12 | get token(): string { 13 | return this.storageService.getItem('auth'); 14 | } 15 | 16 | set token(token: string) { 17 | this.storageService.setItem('auth', token); 18 | } 19 | 20 | get username() { 21 | return this.storageService.getItem('username'); 22 | } 23 | 24 | set username(username: string) { 25 | if (!username) { 26 | this.storageService.removeItem('username'); 27 | } else { 28 | this.storageService.setItem('username', username); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Client/src/app/core/services/desktopCamera.service.js: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | var DesktopCameraService = (function () { 3 | function DesktopCameraService() { 4 | console.log('DesktopCameraService'); 5 | } 6 | DesktopCameraService.prototype.getMediaDevices = function () { 7 | var mediaDevices = ((window.navigator.mozGetUserMedia || window.navigator.webkitGetUserMedia) ? { 8 | getUserMedia: function (options) { 9 | return new Promise(function (resolve, reject) { 10 | (window.navigator.mozGetUserMedia || 11 | window.navigator.webkitGetUserMedia).call(window.navigator, options, resolve, reject); 12 | }); 13 | } 14 | } : null) || window.navigator.mediaDevices; 15 | return mediaDevices; 16 | }; 17 | DesktopCameraService.prototype.getPhoto = function () { 18 | var _this = this; 19 | return Observable.create(function (observer) { 20 | _this.getMediaDevices() 21 | .getUserMedia({ video: true, audio: false }) 22 | .then(function (stream) { 23 | var vendorURL = window.URL || window.webkitURL; 24 | var doc = document; 25 | var videoElement = doc.createElement('video'); 26 | videoElement.src = vendorURL.createObjectURL(stream); 27 | videoElement.play(); 28 | var takePhotoInternal = function () { 29 | var canvasElement = doc.createElement('canvas'); 30 | canvasElement.setAttribute('width', videoElement.videoWidth.toString()); 31 | canvasElement.setAttribute('height', videoElement.videoHeight.toString()); 32 | setTimeout(function () { 33 | var context = canvasElement.getContext('2d'); 34 | context.drawImage(videoElement, 0, 0, videoElement.videoWidth, videoElement.videoHeight); 35 | var url = canvasElement.toDataURL('image/png'); 36 | videoElement.pause(); 37 | if (stream.stop) { 38 | stream.stop(); 39 | } 40 | if (stream.getAudioTracks) { 41 | stream.getAudioTracks().forEach(function (track) { 42 | track.stop(); 43 | }); 44 | } 45 | if (stream.getVideoTracks) { 46 | stream.getVideoTracks().forEach(function (track) { 47 | track.stop(); 48 | }); 49 | } 50 | observer.next(url); 51 | observer.complete(); 52 | }, 500); 53 | }; 54 | if (videoElement.readyState >= videoElement.HAVE_FUTURE_DATA) { 55 | takePhotoInternal(); 56 | } 57 | else { 58 | videoElement.addEventListener('canplay', function () { 59 | takePhotoInternal(); 60 | }, false); 61 | } 62 | }, (function (error) { 63 | console.log(error); 64 | })); 65 | }); 66 | }; 67 | return DesktopCameraService; 68 | }()); 69 | export { DesktopCameraService }; 70 | //# sourceMappingURL=desktopCamera.service.js.map -------------------------------------------------------------------------------- /Client/src/app/core/services/desktopCamera.service.ts: -------------------------------------------------------------------------------- 1 | import { ICameraService } from './camera.service'; 2 | import { Observer } from 'rxjs/Observer'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | declare let window: any; 6 | 7 | export class DesktopCameraService implements ICameraService { 8 | 9 | constructor() { 10 | console.log('DesktopCameraService'); 11 | } 12 | 13 | private getMediaDevices(): any { 14 | const mediaDevices = ((window.navigator.mozGetUserMedia || window.navigator.webkitGetUserMedia) ? { 15 | getUserMedia: function (options: any) { 16 | return new Promise((resolve, reject) => { 17 | (window.navigator.mozGetUserMedia || 18 | window.navigator.webkitGetUserMedia).call(window.navigator, options, resolve, reject); 19 | }); 20 | } 21 | } : null) || window.navigator.mediaDevices; 22 | 23 | return mediaDevices; 24 | } 25 | 26 | getPhoto(): Observable { 27 | return Observable.create((observer: any) => { 28 | this.getMediaDevices() 29 | .getUserMedia({ video: true, audio: false }) 30 | .then((stream: any) => { 31 | let vendorURL = window.URL || window.webkitURL; 32 | let doc = document; 33 | let videoElement = doc.createElement('video'); 34 | videoElement.src = vendorURL.createObjectURL(stream); 35 | videoElement.play(); 36 | 37 | let takePhotoInternal = () => { 38 | let canvasElement = doc.createElement('canvas'); 39 | canvasElement.setAttribute('width', videoElement.videoWidth.toString()); 40 | canvasElement.setAttribute('height', videoElement.videoHeight.toString()); 41 | 42 | // Wait a bit before taking a screenshot so the camera has time to adjust lights 43 | setTimeout(() => { 44 | let context = canvasElement.getContext('2d'); 45 | context.drawImage(videoElement, 0, 0, videoElement.videoWidth, videoElement.videoHeight); 46 | 47 | let url = canvasElement.toDataURL('image/png'); 48 | 49 | videoElement.pause(); 50 | 51 | if (stream.stop) { 52 | stream.stop(); 53 | } 54 | 55 | if (stream.getAudioTracks) { 56 | stream.getAudioTracks().forEach((track: any) => { 57 | track.stop(); 58 | }); 59 | } 60 | 61 | if (stream.getVideoTracks) { 62 | stream.getVideoTracks().forEach((track: any) => { 63 | track.stop(); 64 | }); 65 | } 66 | 67 | observer.next(url); 68 | observer.complete(); 69 | 70 | }, 500); 71 | }; 72 | 73 | if (videoElement.readyState >= videoElement.HAVE_FUTURE_DATA) { 74 | takePhotoInternal() 75 | } else { 76 | videoElement.addEventListener('canplay', function () { 77 | takePhotoInternal() 78 | }, false); 79 | } 80 | 81 | }, ((error: any) => { 82 | console.log(error); 83 | })); 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Client/src/app/core/services/food-data.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from '@angular/common/http/src/response'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observer } from 'rxjs/Observer'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { catchError, map } from 'rxjs/operators'; 6 | 7 | import { CONFIGURATION } from './../../shared/app.constants'; 8 | import { FoodItem } from './../../shared/models/foodItem'; 9 | import { HttpWrapperService } from './httpWrapper.service'; 10 | 11 | @Injectable() 12 | export class FoodDataService { 13 | 14 | private actionUrl: string; 15 | 16 | constructor(private _http: HttpWrapperService) { 17 | this.actionUrl = CONFIGURATION.baseUrls.server + 18 | CONFIGURATION.baseUrls.apiUrl + 19 | 'foods/'; 20 | } 21 | 22 | getAllFood(): Observable { 23 | return this._http.get(this.actionUrl).pipe(catchError(this.handleError)); 24 | } 25 | 26 | getSingleFood(id: number): Observable { 27 | return this._http.get(this.actionUrl + id).pipe(catchError(this.handleError)); 28 | } 29 | 30 | getRandomFood(): Observable { 31 | return this._http.get(this.actionUrl + 'getrandomfood') 32 | .pipe( 33 | map((foodItem: FoodItem) => { 34 | 35 | foodItem.imageString = 36 | CONFIGURATION.baseUrls.server + 37 | CONFIGURATION.baseUrls.foodImageFolder + 38 | foodItem.imageString; 39 | return foodItem; 40 | 41 | }), 42 | catchError(this.handleError) 43 | ); 44 | } 45 | 46 | addFood(foodItem: FoodItem): Observable { 47 | let toAdd: string = JSON.stringify(foodItem); 48 | 49 | return this._http.post(this.actionUrl, toAdd).pipe(catchError(this.handleError)); 50 | } 51 | 52 | updateFood(id: string, foodToUpdate: FoodItem): Observable { 53 | return this._http.put(this.actionUrl + id, JSON.stringify(foodToUpdate)) 54 | .pipe(catchError(this.handleError)); 55 | } 56 | 57 | deleteFood(id: number): Observable { 58 | return this._http.delete(this.actionUrl + id) 59 | .pipe(catchError(this.handleError)); 60 | } 61 | 62 | private handleError(error: HttpResponse) { 63 | console.error(error); 64 | return Observable.throw(error || 'Server error'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Client/src/app/core/services/foodList-data.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from '@angular/common/http/src/response'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { catchError, map } from 'rxjs/operators'; 5 | 6 | import { CONFIGURATION } from './../../shared/app.constants'; 7 | import { FoodItem } from './../../shared/models/foodItem'; 8 | import { FoodList } from './../../shared/models/foodList'; 9 | import { HttpWrapperService } from './httpWrapper.service'; 10 | 11 | @Injectable() 12 | export class FoodListDataService { 13 | 14 | private actionUrl: string; 15 | 16 | constructor(private _http: HttpWrapperService) { 17 | 18 | this.actionUrl = CONFIGURATION.baseUrls.server + 19 | CONFIGURATION.baseUrls.apiUrl + 20 | 'foodlists/'; 21 | } 22 | 23 | getAllLists(): Observable { 24 | return this._http.get(this.actionUrl) 25 | .pipe(catchError(this.handleError)); 26 | } 27 | 28 | getSingleList(id: string): Observable { 29 | return this._http.get(this.actionUrl + id) 30 | .pipe(catchError(this.handleError)); 31 | } 32 | 33 | getFoodFromList(id: string): Observable { 34 | return this._http.get(this.actionUrl + id + '/foods') 35 | .pipe( 36 | map((foodItems: FoodItem[]) => { 37 | foodItems.map((foodItem: FoodItem) => { 38 | foodItem.created = new Date(String(foodItem.created)); 39 | foodItem.imageString = 40 | CONFIGURATION.baseUrls.server + 41 | CONFIGURATION.baseUrls.foodImageFolder + 42 | foodItem.imageString; 43 | console.log(foodItem.imageString); 44 | }); 45 | return foodItems; 46 | }), 47 | catchError(this.handleError)); 48 | } 49 | 50 | addList(foodListName: string): Observable { 51 | let toAdd: string = JSON.stringify({ Name: foodListName }); 52 | 53 | return this._http.post(this.actionUrl, toAdd) 54 | .pipe(catchError(this.handleError)); 55 | } 56 | 57 | updateList(id: string, listToUpdate: FoodList): Observable { 58 | return this._http.put(this.actionUrl + id, JSON.stringify(listToUpdate)) 59 | .pipe(catchError(this.handleError)); 60 | } 61 | 62 | deleteList(id: string): Observable { 63 | return this._http.delete(this.actionUrl + id) 64 | .pipe(catchError(this.handleError)); 65 | } 66 | 67 | getRandomImageStringFromList(id: string): Observable { 68 | return this._http.get(this.actionUrl + id + '/getrandomimage') 69 | .pipe(catchError(this.handleError)); 70 | } 71 | 72 | private handleError(error: HttpResponse) { 73 | console.error(error); 74 | return Observable.throw(error || 'Server error'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Client/src/app/core/services/httpWrapper.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import { CurrentUserService } from '../services/currentUser.service'; 6 | 7 | @Injectable() 8 | export class HttpWrapperService { 9 | constructor(private http: HttpClient, private currentUserService: CurrentUserService) { } 10 | 11 | get(url: string): Observable { 12 | return this.http.get(url); 13 | } 14 | 15 | post(url: string, body: any): Observable { 16 | return this.http.post(url, body); 17 | } 18 | 19 | put(url: string, body: string): Observable { 20 | return this.http.put(url, body); 21 | } 22 | 23 | delete(url: string): Observable { 24 | return this.http.delete(url); 25 | } 26 | 27 | patch(url: string, body: string): Observable { 28 | return this.http.patch(url, body); 29 | } 30 | } 31 | 32 | 33 | @Injectable() 34 | export class MyFirstInterceptor implements HttpInterceptor { 35 | 36 | constructor(private currentUserService: CurrentUserService) { } 37 | 38 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 39 | // console.log(JSON.stringify(req)); 40 | 41 | const token: string = this.currentUserService.token; 42 | 43 | if (token) { 44 | req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) }); 45 | } 46 | 47 | if (!req.headers.has('Content-Type')) { 48 | req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') }); 49 | } 50 | 51 | req = req.clone({ headers: req.headers.set('Accept', 'application/json') }); 52 | return next.handle(req); 53 | } 54 | } -------------------------------------------------------------------------------- /Client/src/app/core/services/mobileCamera.service.js: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Observable'; 2 | var MobileCameraService = (function () { 3 | function MobileCameraService() { 4 | this.getPhoto = function () { 5 | return Observable.create(function (observer) { 6 | var removeDomListener = function () { 7 | document.removeEventListener('deviceready', onCordovaDeviceReady); 8 | }; 9 | var onCordovaDeviceReady = function () { 10 | var camera = window.navigator.camera; 11 | var options = { 12 | quality: 100, 13 | destinationType: camera.DestinationType.DATA_URL, 14 | sourceType: camera.PictureSourceType.CAMERA, 15 | encodingType: camera.EncodingType.PNG, 16 | pictureSourceType: camera.PictureSourceType.CAMERA, 17 | saveToPhotoAlbum: false, 18 | targetWidth: 640, 19 | targetHeight: 640, 20 | correctOrientation: true 21 | }; 22 | camera.getPicture(function (imageData) { 23 | observer.next('data:image/png;base64,' + imageData); 24 | removeDomListener(); 25 | observer.complete(); 26 | }, function (error) { 27 | observer.error(error); 28 | removeDomListener(); 29 | observer.complete(); 30 | }, options); 31 | }; 32 | document.addEventListener('deviceready', onCordovaDeviceReady); 33 | }); 34 | }; 35 | console.log('MobileCameraService'); 36 | } 37 | return MobileCameraService; 38 | }()); 39 | export { MobileCameraService }; 40 | //# sourceMappingURL=mobileCamera.service.js.map -------------------------------------------------------------------------------- /Client/src/app/core/services/mobileCamera.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"mobileCamera.service.js","sourceRoot":"","sources":["mobileCamera.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK7C;IAEI;QAIA,aAAQ,GAAG;YACP,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,UAAC,QAA0B;gBAChD,IAAI,iBAAiB,GAAG;oBACpB,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;gBACtE,CAAC,CAAC;gBAEF,IAAI,oBAAoB,GAAG;oBACvB,IAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;oBAEvC,IAAI,OAAO,GAAG;wBACV,OAAO,EAAE,GAAG;wBACZ,eAAe,EAAE,MAAM,CAAC,eAAe,CAAC,QAAQ;wBAChD,UAAU,EAAE,MAAM,CAAC,iBAAiB,CAAC,MAAM;wBAC3C,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,GAAG;wBACrC,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,CAAC,MAAM;wBAClD,gBAAgB,EAAE,KAAK;wBACvB,WAAW,EAAE,GAAG;wBAChB,YAAY,EAAE,GAAG;wBACjB,kBAAkB,EAAE,IAAI;qBAC3B,CAAC;oBAYF,MAAM,CAAC,UAAU,CAAC,UAAC,SAAc;wBAC7B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAAC;wBACpD,iBAAiB,EAAE,CAAC;wBACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC;oBACxB,CAAC,EAAE,UAAC,KAAU;wBACV,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;wBACtB,iBAAiB,EAAE,CAAC;wBACpB,QAAQ,CAAC,QAAQ,EAAE,CAAC;oBACxB,CAAC,EAAE,OAAO,CAAC,CAAC;gBAChB,CAAC,CAAC;gBAEF,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;YACnE,CAAC,CAAC,CAAC;QACP,CAAC,CAAA;QA/CG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACvC,CAAC;IA+CL,0BAAC;AAAD,CAAC,AAnDD,IAmDC"} -------------------------------------------------------------------------------- /Client/src/app/core/services/mobileCamera.service.ts: -------------------------------------------------------------------------------- 1 | import { ICameraService } from './camera.service'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import { Observer } from 'rxjs/Observer'; 4 | 5 | declare let window: any; 6 | 7 | export class MobileCameraService implements ICameraService { 8 | 9 | constructor() { 10 | console.log('MobileCameraService'); 11 | } 12 | 13 | getPhoto = (): Observable => { 14 | return Observable.create((observer: Observer) => { 15 | let removeDomListener = () => { 16 | document.removeEventListener('deviceready', onCordovaDeviceReady); 17 | }; 18 | 19 | let onCordovaDeviceReady = () => { 20 | const camera = window.navigator.camera; 21 | 22 | let options = { 23 | quality: 100, 24 | destinationType: camera.DestinationType.DATA_URL, 25 | sourceType: camera.PictureSourceType.CAMERA, 26 | encodingType: camera.EncodingType.PNG, 27 | pictureSourceType: camera.PictureSourceType.CAMERA, 28 | saveToPhotoAlbum: false, 29 | targetWidth: 640, 30 | targetHeight: 640, 31 | correctOrientation: true 32 | }; 33 | 34 | // let options = { 35 | // quality: 100, 36 | // destinationType: camera.DestinationType.DATA_URL, 37 | // sourceType: camera.PictureSourceType.CAMERA, 38 | // allowEdit: true, 39 | // encodingType: camera.EncodingType.PNG, 40 | // saveToPhotoAlbum: false, 41 | // correctOrientation: true 42 | // }; 43 | 44 | camera.getPicture((imageData: any) => { 45 | observer.next('data:image/png;base64,' + imageData); 46 | removeDomListener(); 47 | observer.complete(); 48 | }, (error: any) => { 49 | observer.error(error); 50 | removeDomListener(); 51 | observer.complete(); 52 | }, options); 53 | }; 54 | 55 | document.addEventListener('deviceready', onCordovaDeviceReady); 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /Client/src/app/core/services/platformInformation.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | declare let window: any; 4 | 5 | @Injectable() 6 | export class PlatformInformationService { 7 | private _isMobile: boolean; 8 | private _isDesktop: boolean; 9 | private _isWeb: boolean; 10 | 11 | get isMobile(): boolean { 12 | return this._isMobile; 13 | } 14 | 15 | get isDesktop(): boolean { 16 | return this._isDesktop; 17 | } 18 | 19 | get isWeb(): boolean { 20 | return this._isWeb; 21 | } 22 | 23 | constructor() { 24 | this.guessPlatform(); 25 | } 26 | 27 | private guessPlatform(): void { 28 | this._isMobile = !!window.cordova; 29 | this._isDesktop = window.navigator.userAgent.match(/Electron/) !== null; 30 | this._isWeb = !(this._isMobile || this._isDesktop); 31 | console.log('mobile: ' + this._isMobile); 32 | console.log('desktop: ' + this._isDesktop); 33 | console.log('web: ' + this._isWeb); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Client/src/app/core/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class StorageService { 5 | 6 | private _storage: Storage; 7 | 8 | constructor() { 9 | this._storage = localStorage; 10 | } 11 | 12 | setItem(key: string, value: any): void { 13 | this._storage.setItem(key, JSON.stringify(value)); 14 | }; 15 | 16 | removeItem(key: string): void { 17 | this._storage.removeItem(key); 18 | }; 19 | 20 | getItem(key: string): any { 21 | let item: any = this._storage.getItem(key); 22 | 23 | if (item && item !== 'undefined') { 24 | return JSON.parse(this._storage.getItem(key)); 25 | } 26 | 27 | return; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /Client/src/app/food/components/food/food.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 |

Foodlists 8 | See all your food lists 9 |

10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /Client/src/app/food/components/food/food.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { map } from 'rxjs/operators'; 3 | 4 | import { FoodListDataService } from '../../../core/services/foodList-data.service'; 5 | import { FoodList } from '../../../shared/models/foodList'; 6 | 7 | @Component({ 8 | selector: 'food-component', 9 | templateUrl: './food.component.html' 10 | }) 11 | 12 | export class FoodComponent implements OnInit { 13 | 14 | lists: FoodList[] = []; 15 | 16 | constructor(private foodListDataService: FoodListDataService) { } 17 | 18 | addFood(foodListname: string) { 19 | this.foodListDataService 20 | .addList(foodListname) 21 | .pipe(map((response: any) => response.value)) 22 | .subscribe((addedList: FoodList) => { 23 | this.lists.push(addedList); 24 | }); 25 | } 26 | 27 | ngOnInit(): void { 28 | this.foodListDataService 29 | .getAllLists() 30 | .pipe(map((response: any) => response.value)) 31 | .subscribe((lists: FoodList[]) => { 32 | this.lists = lists; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Client/src/app/food/components/foodListDetails/foodListDetails.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 |

8 | {{currentFoodList?.name}}  9 | 10 | 11 | 12 |

13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 |
32 | 43 | 44 | 64 | 65 |
66 |
67 | 68 | 69 |
70 | 71 |
-------------------------------------------------------------------------------- /Client/src/app/food/components/foodListDetails/foodListDetails.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { map, switchMap } from 'rxjs/operators'; 4 | 5 | import { CameraFactory } from './../../../core/factories/cameraFactory'; 6 | import { ICameraService } from './../../../core/services/camera.service'; 7 | import { FoodDataService } from './../../../core/services/food-data.service'; 8 | import { FoodListDataService } from './../../../core/services/foodList-data.service'; 9 | import { FoodItem } from './../../../shared/models/foodItem'; 10 | import { FoodList } from './../../../shared/models/foodList'; 11 | 12 | @Component({ 13 | selector: 'foodListDetails-component', 14 | templateUrl: './foodListDetails.component.html' 15 | }) 16 | 17 | export class FoodListDetails implements OnInit { 18 | 19 | currentFoodList: FoodList; 20 | currentFoodListBackup: FoodList; 21 | currentFood: FoodItem = new FoodItem(); 22 | 23 | private _listId: string; 24 | private _cameraService: ICameraService; 25 | 26 | constructor( 27 | private _router: Router, 28 | private _activatedRoute: ActivatedRoute, 29 | private _foodDataService: FoodDataService, 30 | private _foodListDataService: FoodListDataService, 31 | private _cameraFactory: CameraFactory, 32 | private _ngZone: NgZone) { 33 | 34 | this._cameraService = _cameraFactory.getCameraService(); 35 | } 36 | 37 | ngOnInit() { 38 | this._activatedRoute.params.subscribe(params => { 39 | this._listId = params['foodId']; 40 | this.getSingleList(this._listId); 41 | }); 42 | } 43 | 44 | private getSingleList(listId: string) { 45 | this._foodListDataService 46 | .getSingleList(listId) 47 | .pipe(map((response: FoodList) => { 48 | this.currentFoodList = response; 49 | }), 50 | switchMap(() => { 51 | return this._foodListDataService.getFoodFromList(listId) 52 | })) 53 | .subscribe((foodItems: FoodItem[]) => { 54 | this.currentFoodList.foods = foodItems; 55 | this.currentFoodListBackup = Object.assign({}, this.currentFoodList); 56 | }); 57 | } 58 | 59 | togglePublic(food: FoodItem) { 60 | food.isPublic = !food.isPublic; 61 | this._foodDataService 62 | .updateFood(food.id, food) 63 | .subscribe((response: FoodItem) => { 64 | console.log('updated'); 65 | }); 66 | } 67 | 68 | showRandomFoodFromList() { 69 | 70 | const foodCount = this.currentFoodListBackup.foods.length; 71 | 72 | if (this.currentFoodList.foods.length > 0) { 73 | let foodToShow: FoodItem[] = []; 74 | let index = Math.floor((Math.random() * foodCount)); 75 | console.log(index); 76 | foodToShow.push(this.currentFoodListBackup.foods[index]); 77 | this.currentFoodList.foods = foodToShow; 78 | } 79 | } 80 | 81 | setToUpdate(foodItem: FoodItem) { 82 | this.currentFood = foodItem; 83 | } 84 | 85 | addOrUpdateFood() { 86 | if (this.currentFood.id) { 87 | this.updateFood(this.currentFood); 88 | } else { 89 | this.addFood(this.currentFood); 90 | } 91 | } 92 | 93 | private updateFood(foodItem: FoodItem) { 94 | this._foodDataService 95 | .updateFood(foodItem.id, foodItem) 96 | .subscribe((response: FoodItem) => { 97 | this.currentFood = new FoodItem(); 98 | }, error => console.log(error)); 99 | } 100 | 101 | private addFood(foodItem: FoodItem) { 102 | if (foodItem.itemName) { 103 | foodItem.foodListId = this.currentFoodList.id; 104 | 105 | this._foodDataService 106 | .addFood(foodItem) 107 | .pipe(map((response: FoodItem) => { 108 | this.currentFood = new FoodItem(); 109 | }), 110 | switchMap(() => { 111 | return this._foodListDataService.getFoodFromList(foodItem.foodListId) 112 | })) 113 | .subscribe((foodItems: FoodItem[]) => { 114 | this.currentFoodList.foods = foodItems; 115 | }); 116 | } 117 | } 118 | 119 | deleteList() { 120 | if (this.currentFoodList) { 121 | this._foodListDataService 122 | .deleteList(this.currentFoodList.id) 123 | .subscribe((response: any) => { 124 | let link = ['/foodlists']; 125 | this._router.navigate(link); 126 | }, error => console.log(error)); 127 | } 128 | } 129 | 130 | deleteFood(foodId: number) { 131 | 132 | this._foodDataService 133 | .deleteFood(foodId) 134 | .subscribe((response: any) => { 135 | 136 | }, error => console.log(error)); 137 | 138 | } 139 | 140 | takePhoto(foodItem: FoodItem) { 141 | this._cameraService 142 | .getPhoto() 143 | .pipe( 144 | map((url: string) => { 145 | foodItem.imageString = url; 146 | return foodItem; 147 | }), 148 | switchMap((item: FoodItem) => { 149 | return this._foodDataService.updateFood(item.id, item) 150 | }), 151 | switchMap(() => { 152 | return this._foodListDataService.getFoodFromList(foodItem.foodListId) 153 | }) 154 | ) 155 | .subscribe((foodItems: FoodItem[]) => { 156 | this.currentFoodList.foods = foodItems; 157 | }); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Client/src/app/food/components/foodListForm/foodListForm.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 7 | 8 |
9 | Pflichtfeld! 10 |
11 | 12 |
13 | 14 |
15 |
16 |
-------------------------------------------------------------------------------- /Client/src/app/food/components/foodListForm/foodListForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | import { FoodListDataService } from './../../../core/services/foodList-data.service'; 4 | import { FoodList } from './../../../shared/models/foodList'; 5 | 6 | @Component({ 7 | selector: 'foodListForm-component', 8 | templateUrl: './foodListForm.component.html' 9 | }) 10 | 11 | export class FoodListFormComponent { 12 | 13 | @Output() onFoodListAdded = new EventEmitter(); 14 | list: FoodList = new FoodList(); 15 | 16 | constructor() { } 17 | 18 | addList() { 19 | this.onFoodListAdded.emit(this.list.name); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Client/src/app/food/components/foodlists/foodlists.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |
8 | 9 |
10 |
11 | 24 | 25 |
26 | 27 | 28 |
29 |
30 |
-------------------------------------------------------------------------------- /Client/src/app/food/components/foodlists/foodlists.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | import { FoodList } from './../../../shared/models/foodList'; 4 | 5 | @Component({ 6 | selector: 'foodlists-component', 7 | templateUrl: './foodlists.component.html' 8 | }) 9 | 10 | export class FoodListComponent { 11 | @Input() allLists: FoodList[] = []; 12 | } 13 | -------------------------------------------------------------------------------- /Client/src/app/food/food.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { FoodComponent } from './components/food/food.component'; 7 | import { FoodListDetails } from './components/foodListDetails/foodListDetails.component'; 8 | import { FoodListFormComponent } from './components/foodListForm/foodListForm.component'; 9 | import { FoodListComponent } from './components/foodlists/foodlists.component'; 10 | import { FoodRoutes } from './food.routes'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule, 15 | HttpClientModule, 16 | FormsModule, 17 | FoodRoutes 18 | ], 19 | 20 | declarations: [ 21 | FoodListComponent, 22 | FoodComponent, 23 | FoodListDetails, 24 | FoodListFormComponent 25 | ] 26 | }) 27 | 28 | export class FoodModule { } 29 | -------------------------------------------------------------------------------- /Client/src/app/food/food.routes.js: -------------------------------------------------------------------------------- 1 | import { NeedsAuthentication } from './../shared/decorators/needsAuthentication'; 2 | import { FoodListDetails } from './components/foodListDetails/foodListDetails.component'; 3 | import { FoodComponent } from './components/food/food.component'; 4 | import { RouterModule } from '@angular/router'; 5 | var foodRoutes = [ 6 | { path: '', component: FoodComponent, canActivate: [NeedsAuthentication] }, 7 | { path: ':foodId', component: FoodListDetails, canActivate: [NeedsAuthentication] }, 8 | ]; 9 | export var FoodRoutes = RouterModule.forChild(foodRoutes); 10 | //# sourceMappingURL=food.routes.js.map -------------------------------------------------------------------------------- /Client/src/app/food/food.routes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"food.routes.js","sourceRoot":"","sources":["food.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,wDAAwD,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAU,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEvD,IAAM,UAAU,GAAW;IACvB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC1E,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,CAAC,mBAAmB,CAAC,EAAE;CACtF,CAAC;AAEF,MAAM,CAAC,IAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/food/food.routes.ts: -------------------------------------------------------------------------------- 1 | import { NeedsAuthentication } from './../shared/decorators/needsAuthentication'; 2 | import { FoodListDetails } from './components/foodListDetails/foodListDetails.component'; 3 | import { FoodComponent } from './components/food/food.component'; 4 | import { Routes, RouterModule } from '@angular/router'; 5 | 6 | const foodRoutes: Routes = [ 7 | { path: '', component: FoodComponent, canActivate: [NeedsAuthentication] }, 8 | { path: ':foodId', component: FoodListDetails, canActivate: [NeedsAuthentication] }, 9 | ]; 10 | 11 | export const FoodRoutes = RouterModule.forChild(foodRoutes); 12 | -------------------------------------------------------------------------------- /Client/src/app/home/components/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

{{randomFood?.itemName}}

6 | 7 | 8 |
9 |

{{errorMessage}}

10 | 16 | 17 |

Getting your food suggestions
Please wait...

18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /Client/src/app/home/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { AuthenticationService } from './../../../core/services/authentication.service'; 4 | import { FoodDataService } from './../../../core/services/food-data.service'; 5 | import { FoodItem } from './../../../shared/models/foodItem'; 6 | 7 | @Component({ 8 | selector: 'home-component', 9 | templateUrl: './home.component.html' 10 | }) 11 | 12 | export class HomeComponent implements OnInit { 13 | 14 | randomFood: FoodItem; 15 | errorMessage: string; 16 | 17 | constructor( 18 | private foodDataService: FoodDataService, 19 | public authenticationService: AuthenticationService) { } 20 | 21 | ngOnInit() { 22 | this.getRandomFood(); 23 | } 24 | 25 | getRandomFood() { 26 | this.foodDataService 27 | .getRandomFood() 28 | .subscribe((response: FoodItem) => { 29 | this.randomFood = response; 30 | }, (error: any) => { 31 | if (error.status === 400) { 32 | this.errorMessage = 'No food found :-('; 33 | } else { 34 | this.errorMessage = 'There was an error'; 35 | } 36 | }); 37 | } 38 | 39 | getRecipesWithGoogle(): void { 40 | window.open('https://www.google.de/search?q=' + this.randomFood.itemName, '_blank'); 41 | } 42 | 43 | getRecipesWithBing(): void { 44 | window.open('https://www.bing.com/search?q=' + this.randomFood.itemName, '_blank'); 45 | } 46 | } -------------------------------------------------------------------------------- /Client/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { HttpClientModule } from '@angular/common/http'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { HomeComponent } from './components/home/home.component'; 7 | import { HomeRoutes } from './home.routes'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | HomeRoutes, 13 | HttpClientModule, 14 | FormsModule 15 | ], 16 | 17 | declarations: [ 18 | HomeComponent 19 | ], 20 | 21 | providers: [ 22 | 23 | ] 24 | }) 25 | 26 | export class HomeModule { } 27 | -------------------------------------------------------------------------------- /Client/src/app/home/home.routes.js: -------------------------------------------------------------------------------- 1 | import { HomeComponent } from './components/home/home.component'; 2 | import { RouterModule } from '@angular/router'; 3 | var appRoutes = [ 4 | { path: 'home', component: HomeComponent } 5 | ]; 6 | export var HomeRoutes = RouterModule.forChild(appRoutes); 7 | //# sourceMappingURL=home.routes.js.map -------------------------------------------------------------------------------- /Client/src/app/home/home.routes.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"home.routes.js","sourceRoot":"","sources":["home.routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAU,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEvD,IAAM,SAAS,GAAW;IACtB,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE;CAC7C,CAAC;AAEF,MAAM,CAAC,IAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/home/home.routes.ts: -------------------------------------------------------------------------------- 1 | import { HomeComponent } from './components/home/home.component'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const appRoutes: Routes = [ 5 | { path: 'home', component: HomeComponent } 6 | ]; 7 | 8 | export const HomeRoutes = RouterModule.forChild(appRoutes); 9 | -------------------------------------------------------------------------------- /Client/src/app/layout/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /Client/src/app/layout/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { AuthenticationService } from './../../../core/services/authentication.service'; 4 | 5 | @Component({ 6 | selector: 'header-component', 7 | templateUrl: './header.component.html' 8 | }) 9 | 10 | export class HeaderComponent { 11 | 12 | constructor(public authenticationService: AuthenticationService) { } 13 | 14 | logout() { 15 | this.authenticationService.logoutUser(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Client/src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule } from '@angular/router'; 2 | import { HeaderComponent } from './components/header/header.component'; 3 | import { CommonModule } from '@angular/common'; 4 | import { NgModule } from '@angular/core'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule, 9 | RouterModule 10 | ], 11 | 12 | declarations: [ 13 | HeaderComponent 14 | ], 15 | 16 | providers: [ 17 | ], 18 | 19 | exports: [ 20 | HeaderComponent 21 | ] 22 | }) 23 | 24 | export class LayoutModule { } 25 | -------------------------------------------------------------------------------- /Client/src/app/main-aot.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["main-aot.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,OAAO,aAAa,CAAC;AACrB,OAAO,UAAU,CAAC;AAElB,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,cAAc,EAAE,CAAC;AAEjB,eAAe,EAAE,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC","file":"main-aot.js","sourceRoot":""} -------------------------------------------------------------------------------- /Client/src/app/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AACrB,OAAO,UAAU,CAAC;AAElB,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,sBAAsB,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/polyfills.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"polyfills.js","sourceRoot":"","sources":["polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAC;AAEjB,OAAO,oBAAoB,CAAC;AAC5B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAC/B,OAAO,yBAAyB,CAAC;AACjC,OAAO,oBAAoB,CAAC;AAC5B,OAAO,kBAAkB,CAAC;AAC1B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,kBAAkB,CAAC;AAC1B,OAAO,mBAAmB,CAAC;AAC3B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,iBAAiB,CAAC;AACzB,OAAO,iBAAiB,CAAC;AACzB,OAAO,sBAAsB,CAAC;AAC9B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,mBAAmB,CAAC;AAC3B,OAAO,qBAAqB,CAAC;AAC7B,OAAO,qBAAqB,CAAC;AAE7B,OAAO,mBAAmB,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/shared/app.constants.js: -------------------------------------------------------------------------------- 1 | export var CONFIGURATION = { 2 | baseUrls: { 3 | server: 'http://localhost:64943/', 4 | apiUrl: 'api/', 5 | foodImageFolder: 'foodimages/' 6 | }, 7 | authConfig: { 8 | CLIENT_ID: 'AngularFoodClient', 9 | GRANT_TYPE: 'password', 10 | SCOPE: 'WebAPI' 11 | } 12 | }; 13 | //# sourceMappingURL=app.constants.js.map -------------------------------------------------------------------------------- /Client/src/app/shared/app.constants.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.constants.js","sourceRoot":"","sources":["app.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,IAAI,aAAa,GAAG;IACzB,QAAQ,EAAE;QACR,MAAM,EAAE,yBAAyB;QAEjC,MAAM,EAAE,MAAM;QACd,eAAe,EAAE,aAAa;KAC/B;IACD,UAAU,EAAE;QACV,SAAS,EAAE,mBAAmB;QAC9B,UAAU,EAAE,UAAU;QACtB,KAAK,EAAE,QAAQ;KAChB;CACF,CAAC"} -------------------------------------------------------------------------------- /Client/src/app/shared/app.constants.ts: -------------------------------------------------------------------------------- 1 | export let CONFIGURATION = { 2 | baseUrls: { 3 | // server: 'http://localhost:64943/', 4 | server: 'http://foodchooser.azurewebsites.net/', 5 | apiUrl: 'api/', 6 | foodImageFolder: 'foodimages/' 7 | }, 8 | authConfig: { 9 | CLIENT_ID: 'AngularFoodClient', 10 | GRANT_TYPE: 'password', 11 | SCOPE: 'WebAPI' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /Client/src/app/shared/decorators/needsAuthentication.ts: -------------------------------------------------------------------------------- 1 | import { StorageService } from './../../core/services/storage.service'; 2 | import { CanActivate, Router } from '@angular/router'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable() 6 | export class NeedsAuthentication implements CanActivate { 7 | constructor(private _storeageService: StorageService, private _router: Router) { 8 | 9 | } 10 | canActivate() { 11 | if (this._storeageService.getItem('auth')) { 12 | return true; 13 | } 14 | 15 | this._router.navigate(['/login']); 16 | return false; 17 | } 18 | } -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodItem.js: -------------------------------------------------------------------------------- 1 | var FoodItem = (function () { 2 | function FoodItem() { 3 | } 4 | return FoodItem; 5 | }()); 6 | export { FoodItem }; 7 | //# sourceMappingURL=foodItem.js.map -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodItem.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"foodItem.js","sourceRoot":"","sources":["foodItem.ts"],"names":[],"mappings":"AAEA;IAAA;IAQA,CAAC;IAAD,eAAC;AAAD,CAAC,AARD,IAQC"} -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodItem.ts: -------------------------------------------------------------------------------- 1 | import { CONFIGURATION } from '../app.constants'; 2 | 3 | export class FoodItem { 4 | id: string; 5 | foodListId: string; 6 | itemName: string; 7 | pictureUrl: string; 8 | created: Date; 9 | isPublic: Boolean; 10 | imageString: string; 11 | } 12 | -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodList.js: -------------------------------------------------------------------------------- 1 | import { CONFIGURATION } from './../app.constants'; 2 | var FoodList = (function () { 3 | function FoodList() { 4 | this._imageString = ''; 5 | } 6 | Object.defineProperty(FoodList.prototype, "imageString", { 7 | get: function () { 8 | return this._imageString; 9 | }, 10 | enumerable: true, 11 | configurable: true 12 | }); 13 | FoodList.prototype.setImage = function (imageString) { 14 | if (imageString) { 15 | this._imageString = CONFIGURATION.baseUrls.server + imageString; 16 | } 17 | }; 18 | return FoodList; 19 | }()); 20 | export { FoodList }; 21 | //# sourceMappingURL=foodList.js.map -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodList.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"foodList.js","sourceRoot":"","sources":["foodList.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD;IAAA;QACY,iBAAY,GAAG,EAAE,CAAC;IAa9B,CAAC;IATG,sBAAI,iCAAW;aAAf;YACI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;QAC7B,CAAC;;;OAAA;IAED,2BAAQ,GAAR,UAAS,WAAmB;QACxB,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;YACd,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,MAAM,GAAG,WAAW,CAAC;QACpE,CAAC;IACL,CAAC;IACL,eAAC;AAAD,CAAC,AAdD,IAcC"} -------------------------------------------------------------------------------- /Client/src/app/shared/models/foodList.ts: -------------------------------------------------------------------------------- 1 | import { CONFIGURATION } from './../app.constants'; 2 | import { FoodItem } from './FoodItem'; 3 | 4 | export class FoodList { 5 | private _imageString = ''; 6 | id: string; 7 | name: string; 8 | foods: FoodItem[]; 9 | get imageString(): string { 10 | return this._imageString; 11 | } 12 | 13 | setImage(imageString: string) { 14 | if (imageString) { 15 | this._imageString = CONFIGURATION.baseUrls.server + imageString; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Client/src/app/shared/models/loginUser.js: -------------------------------------------------------------------------------- 1 | var LoginUser = (function () { 2 | function LoginUser() { 3 | } 4 | return LoginUser; 5 | }()); 6 | export { LoginUser }; 7 | //# sourceMappingURL=loginUser.js.map -------------------------------------------------------------------------------- /Client/src/app/shared/models/loginUser.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"loginUser.js","sourceRoot":"","sources":["loginUser.ts"],"names":[],"mappings":"AACA;IAAA;IAGA,CAAC;IAAD,gBAAC;AAAD,CAAC,AAHD,IAGC"} -------------------------------------------------------------------------------- /Client/src/app/shared/models/loginUser.ts: -------------------------------------------------------------------------------- 1 | 2 | export class LoginUser { 3 | Username: string; 4 | Password: string; 5 | } -------------------------------------------------------------------------------- /Client/src/app/shared/models/token.js: -------------------------------------------------------------------------------- 1 | var Token = (function () { 2 | function Token() { 3 | } 4 | return Token; 5 | }()); 6 | export { Token }; 7 | //# sourceMappingURL=token.js.map -------------------------------------------------------------------------------- /Client/src/app/shared/models/token.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"token.js","sourceRoot":"","sources":["token.ts"],"names":[],"mappings":"AAAA;IAAA;IAIA,CAAC;IAAD,YAAC;AAAD,CAAC,AAJD,IAIC"} -------------------------------------------------------------------------------- /Client/src/app/shared/models/token.ts: -------------------------------------------------------------------------------- 1 | export class Token { 2 | access_token: string; 3 | token_type: string; 4 | expires_in: number; 5 | } -------------------------------------------------------------------------------- /Client/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NeedsAuthentication } from './decorators/needsAuthentication'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | 10 | declarations: [ 11 | 12 | ], 13 | 14 | providers: [ 15 | NeedsAuthentication 16 | ] 17 | }) 18 | 19 | export class SharedModule { } 20 | -------------------------------------------------------------------------------- /Client/src/app/vendor.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendor.js","sourceRoot":"","sources":["vendor.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,6BAA6B,CAAC;AACrC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,wCAAwC,CAAC;AAEhD,OAAO,mBAAmB,CAAC;AAE3B,OAAO,iCAAiC,CAAC"} -------------------------------------------------------------------------------- /Client/src/css/custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | background:url('../../img/background.jpg') no-repeat center center; 3 | min-height:100%; 4 | background-size:cover; 5 | } 6 | 7 | body { 8 | margin-top: 50px; /* Required margin for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ 9 | background: transparent; 10 | min-height:100%; 11 | padding-top: 40px; 12 | padding-bottom: 40px; 13 | } 14 | 15 | .centered { 16 | position: absolute; 17 | top: 50%; 18 | left: 50%; 19 | transform: translate(-50%,-50%); 20 | } 21 | 22 | .starter-template { 23 | padding: 40px 15px; 24 | text-align: center; 25 | } 26 | 27 | h1 { 28 | font-family: Arial, Helvetica, sans-serif; 29 | font-size: 250%; 30 | } 31 | 32 | .form-signin { 33 | max-width: 330px; 34 | padding: 15px; 35 | margin: 0 auto; 36 | } 37 | .form-signin .form-signin-heading, 38 | .form-signin .checkbox { 39 | margin-bottom: 10px; 40 | } 41 | .form-signin .checkbox { 42 | font-weight: normal; 43 | } 44 | .form-signin .form-control { 45 | position: relative; 46 | height: auto; 47 | -webkit-box-sizing: border-box; 48 | -moz-box-sizing: border-box; 49 | box-sizing: border-box; 50 | padding: 10px; 51 | font-size: 16px; 52 | } 53 | .form-signin .form-control:focus { 54 | z-index: 2; 55 | } 56 | .form-signin input[type="email"] { 57 | margin-bottom: -1px; 58 | border-bottom-right-radius: 0; 59 | border-bottom-left-radius: 0; 60 | } 61 | .form-signin input[type="password"] { 62 | margin-bottom: 10px; 63 | border-top-left-radius: 0; 64 | border-top-right-radius: 0; 65 | } 66 | 67 | .img-responsive{ 68 | max-width: 100%; 69 | height: 200; 70 | display:block ; 71 | } -------------------------------------------------------------------------------- /Client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Client/src/favicon.ico -------------------------------------------------------------------------------- /Client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FoodChooser Angular 5 | 6 | 7 | 8 | 9 | Loading... 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Client/src/main-aot.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowser } from '@angular/platform-browser'; 3 | 4 | import { AppModuleNgFactory } from './app/app.module.ngfactory'; 5 | 6 | enableProdMode(); 7 | 8 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); 9 | -------------------------------------------------------------------------------- /Client/src/main.js: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { AppModule } from './app/app.module'; 3 | platformBrowserDynamic().bootstrapModule(AppModule); 4 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /Client/src/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,sBAAsB,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC"} -------------------------------------------------------------------------------- /Client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /Client/src/polyfills.js: -------------------------------------------------------------------------------- 1 | import 'ie-shim'; 2 | import 'core-js/es6/array'; 3 | import 'core-js/es6/date'; 4 | import 'core-js/es6/function'; 5 | import 'core-js/es6/map'; 6 | import 'core-js/es6/math'; 7 | import 'core-js/es6/number'; 8 | import 'core-js/es6/object'; 9 | import 'core-js/es6/parse-float'; 10 | import 'core-js/es6/parse-int'; 11 | import 'core-js/es6/reflect'; 12 | import 'core-js/es6/regexp'; 13 | import 'core-js/es6/set'; 14 | import 'core-js/es6/string'; 15 | import 'core-js/es6/symbol'; 16 | import 'core-js/es6/typed'; 17 | import 'core-js/es6/weak-map'; 18 | import 'core-js/es6/weak-set'; 19 | import 'core-js/es7/reflect'; 20 | import 'zone.js/dist/zone'; 21 | //# sourceMappingURL=polyfills.js.map -------------------------------------------------------------------------------- /Client/src/polyfills.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"polyfills.js","sourceRoot":"","sources":["polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAC;AACjB,OAAO,mBAAmB,CAAC;AAC3B,OAAO,kBAAkB,CAAC;AAC1B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,iBAAiB,CAAC;AACzB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,yBAAyB,CAAC;AACjC,OAAO,uBAAuB,CAAC;AAC/B,OAAO,qBAAqB,CAAC;AAC7B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,iBAAiB,CAAC;AACzB,OAAO,oBAAoB,CAAC;AAC5B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,mBAAmB,CAAC;AAC3B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,qBAAqB,CAAC;AAC7B,OAAO,mBAAmB,CAAC"} -------------------------------------------------------------------------------- /Client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'ie-shim'; 2 | import 'core-js/es6/array'; 3 | import 'core-js/es6/date'; 4 | import 'core-js/es6/function'; 5 | import 'core-js/es6/map'; 6 | import 'core-js/es6/math'; 7 | import 'core-js/es6/number'; 8 | import 'core-js/es6/object'; 9 | import 'core-js/es6/parse-float'; 10 | import 'core-js/es6/parse-int'; 11 | import 'core-js/es6/reflect'; 12 | import 'core-js/es6/regexp'; 13 | import 'core-js/es6/set'; 14 | import 'core-js/es6/string'; 15 | import 'core-js/es6/symbol'; 16 | import 'core-js/es6/typed'; 17 | import 'core-js/es6/weak-map'; 18 | import 'core-js/es6/weak-set'; 19 | import 'core-js/es7/reflect'; 20 | import 'zone.js/dist/zone'; 21 | 22 | -------------------------------------------------------------------------------- /Client/src/vendor.js: -------------------------------------------------------------------------------- 1 | import 'jquery/src/jquery'; 2 | import 'bootstrap/dist/js/bootstrap'; 3 | import '../assets/toggleHamburger.js'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import 'bootstrap/dist/css/bootstrap-theme.css'; 6 | import './css/custom.css'; 7 | //# sourceMappingURL=vendor.js.map -------------------------------------------------------------------------------- /Client/src/vendor.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendor.js","sourceRoot":"","sources":["vendor.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,6BAA6B,CAAC;AACrC,OAAO,8BAA8B,CAAC;AAEtC,OAAO,kCAAkC,CAAC;AAC1C,OAAO,wCAAwC,CAAC;AAChD,OAAO,kBAAkB,CAAC"} -------------------------------------------------------------------------------- /Client/src/vendor.ts: -------------------------------------------------------------------------------- 1 | import 'jquery/src/jquery'; 2 | import 'bootstrap/dist/js/bootstrap'; 3 | import '../assets/toggleHamburger.js'; 4 | 5 | import 'bootstrap/dist/css/bootstrap.css'; 6 | import 'bootstrap/dist/css/bootstrap-theme.css'; 7 | import './css/custom.css'; 8 | 9 | 10 | -------------------------------------------------------------------------------- /Client/tsconfig-aot.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": true, 10 | "skipLibCheck": true, 11 | "lib": [ 12 | "es2015", 13 | "dom" 14 | ] 15 | }, 16 | "angularCompilerOptions": { 17 | "entryModule": "./src/app/app.module#AppModule", 18 | "skipMetadataEmit": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": true, 10 | "noImplicitAny": true, 11 | "skipLibCheck": true, 12 | "lib": [ 13 | "es2015", 14 | "dom" 15 | ], 16 | "typeRoots": [ 17 | "./node_modules/@types/" 18 | ] 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "./src/main-aot.ts" 23 | ], 24 | "awesomeTypescriptLoaderOptions": { 25 | "useWebpackText": true 26 | }, 27 | "compileOnSave": false, 28 | "buildOnSave": false 29 | } 30 | -------------------------------------------------------------------------------- /Client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [true, "ignore-params"], 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Client/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (env) { 2 | console.log(env); 3 | return require(`./webpack.${env}.js`); 4 | } -------------------------------------------------------------------------------- /Client/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | const path = require('path'); 6 | 7 | module.exports = { 8 | devtool: 'cheap-module-eval-source-map', 9 | performance: { 10 | hints: false 11 | }, 12 | 13 | entry: { 14 | 'polyfills': './src/polyfills.ts', 15 | 'vendor': './src/vendor.ts', 16 | 'app': './src/main.ts' // JiT compilation 17 | }, 18 | 19 | output: { 20 | path: __dirname + '/.dist/web/jit/', 21 | filename: 'js/[name].bundle.js', 22 | chunkFilename: 'js/[id].chunk.js' 23 | }, 24 | 25 | resolve: { 26 | extensions: ['.ts', '.js', '.json'] 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.ts$/, 33 | use: [ 34 | 'awesome-typescript-loader', 35 | 'angular-router-loader', 36 | 'angular2-template-loader', 37 | 'source-map-loader' 38 | ] 39 | }, 40 | { 41 | test: /\.html$/, 42 | use: 'raw-loader' 43 | }, 44 | { 45 | test: /\.(png|jpg|gif|ico|woff|woff2|ttf|svg|eot)$/, 46 | use: 'file-loader?name=assets/[name].[ext]', 47 | }, 48 | { 49 | test: /\.css$/, 50 | use: ['style-loader', 'css-loader'] 51 | } 52 | ] 53 | }, 54 | 55 | plugins: [ 56 | 57 | new HtmlWebpackPlugin({ 58 | filename: 'index.html', 59 | inject: 'body', 60 | template: './src/index.html' 61 | }), 62 | 63 | new webpack.ContextReplacementPlugin( 64 | /angular(\\|\/)core(\\|\/)@angular/, 65 | path.resolve(__dirname, './src') 66 | ), 67 | 68 | new webpack.optimize.CommonsChunkPlugin( 69 | { 70 | name: ['vendor', 'polyfills'] 71 | }), 72 | 73 | new webpack.ProvidePlugin({ 74 | jQuery: 'jquery', 75 | $: 'jquery', 76 | jquery: 'jquery' 77 | }) 78 | ] 79 | }; -------------------------------------------------------------------------------- /Client/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | const CompressionPlugin = require("compression-webpack-plugin"); 6 | const path = require('path'); 7 | const ngToolsWebpack = require('@ngtools/webpack'); 8 | const rxPaths = require('rxjs/_esm5/path-mapping'); 9 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 10 | 11 | module.exports = { 12 | entry: { 13 | 'polyfills': './src/polyfills.ts', 14 | 'vendor': './src/vendor.ts', 15 | 'app': './src/main-aot.ts' // AoT compilation 16 | }, 17 | 18 | output: { 19 | path: path.join(__dirname, '.dist/web/aot/'), 20 | filename: 'js/[name]-[hash:8].bundle.js', 21 | chunkFilename: 'js/[id]-[hash:8].chunk.js', 22 | }, 23 | 24 | resolve: { 25 | extensions: ['.ts', '.js', '.json'], 26 | alias: rxPaths() 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | { 32 | test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, 33 | use: '@ngtools/webpack' 34 | }, 35 | { 36 | test: /\.html$/, 37 | use: 'raw-loader' 38 | }, 39 | { 40 | test: /\.(png|jpg|gif|ico|woff|woff2|ttf|svg|eot)$/, 41 | use: 'file-loader?name=assets/[name]-[hash:6].[ext]', 42 | }, 43 | { 44 | test: /\.css$/, 45 | use: ExtractTextPlugin.extract('css-loader') 46 | } 47 | ], 48 | exprContextCritical: false 49 | }, 50 | plugins: [ 51 | new BundleAnalyzerPlugin({ 52 | analyzerMode: 'static' 53 | }), 54 | 55 | new CleanWebpackPlugin( 56 | [ 57 | './.dist/web/aot/' 58 | ] 59 | ), 60 | 61 | new ngToolsWebpack.AngularCompilerPlugin({ 62 | tsConfigPath: './tsconfig-aot.json' 63 | }), 64 | 65 | new webpack.optimize.ModuleConcatenationPlugin(), 66 | 67 | new webpack.optimize.UglifyJsPlugin({ 68 | compress: { 69 | warnings: false 70 | }, 71 | output: { 72 | comments: false 73 | }, 74 | sourceMap: false 75 | }), 76 | 77 | new CompressionPlugin({ 78 | asset: "[path].gz[query]", 79 | algorithm: "gzip", 80 | test: /\.js$|\.html$/, 81 | threshold: 10240, 82 | minRatio: 0.8 83 | }), 84 | 85 | new ExtractTextPlugin("styles.css"), 86 | 87 | new webpack.optimize.CommonsChunkPlugin( 88 | { 89 | name: ['vendor', 'polyfills'] 90 | }), 91 | 92 | new HtmlWebpackPlugin({ 93 | filename: 'index.html', 94 | inject: 'body', 95 | template: './src/index.html' 96 | }), 97 | 98 | new webpack.ProvidePlugin({ 99 | jQuery: 'jquery', 100 | $: 'jquery', 101 | jquery: 'jquery' 102 | }) 103 | ] 104 | }; -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # ASP.NET // Webpack // Angular // Typescript // Ahead of Time (AoT) // TreeShaking // Cordova // Electron // Cross Platform - Project "Foodchooser" 2 | 3 | > Get the Android SDK here [Android SDK](https://developer.android.com/sdk/index.html/ "Android SDK") 4 | > Get the Windows SDK here [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk "Windows SDK") 5 | 6 | > Make sure you have installed
7 | > Typescript compiler: `tsc -v` = Version 1.8.10
8 | > Npm: `npm -v` = 2.15.1
9 | > Node `node -v` = v4.4.4
10 | > Cordova: `cordova -v` = 6.1.1
11 | 12 | ## Blogposts 13 | [Angular, ASP.NET WebAPI, Azure & Cordova, Cross Platform – My Private Hackathon Part 1](http://offering.solutions/blog/articles/2016/04/19/angular-asp-net-webapi-azure-cordova-cross-platform-2/) 14 | 15 | [Angular, ASP.NET WebAPI, Azure & Cordova, Cross Platform – My Private Hackathon Part 2](http://offering.solutions/blog/articles/2016/04/26/angular-asp-net-webapi-azure-cordova-cross-platform-my-private-hackathon-part-2/) 16 | 17 | 18 | ### In this repository we have done an example app with 19 | 20 | * Angular 21 | * Electron 22 | * Cordova 23 | * Typescript 24 | * Bootstrap 25 | * Automapper 26 | * WebPack 27 | * ASP.NET WebAPI 2.2 28 | * Gulp.js 29 | * NPM 30 | * Azure 31 | 32 | ### to make it cross platform, which means its working as an exe, as an app on phones and as a web application. 33 | 34 | ### This is done with 35 | 36 | * Cordova (for the Apps) 37 | * Electron (for the exe) 38 | * Gulp (for the web application) 39 | * WebPack (for the web application) 40 | * ASP.NET WebAPI (for the backend) 41 | 42 | ### Feel free to add gulp tasks as you want :) 43 | 44 | ## This is the Angular 2 Version of the Foodchooser-Example. Find the Angular 1 Version here: [Angular1 Version](https://github.com/FabianGosebrink/ASPNET-Foodchooser-Cross-Platform-Angular1 "Foodchooser - ASP.NET WebAPI AngularJs Cross Platform") 45 | 46 | # Start 47 | 48 | Navigate to the "Client"-Folder and type in 49 | ``` 50 | npm install 51 | ``` 52 | and 53 | ``` 54 | tsc 55 | ``` 56 | to install all dependencies and run 57 | 58 | ``` 59 | npm start 60 | ``` 61 | and browse to "localhost:8080" to start the application. 62 | 63 | ![Alternativtext](.github/webpack-server.png "GulpTasks") 64 | 65 | If you want to start right ahead with the gulp tasks listed below just make sure you ran the
tsc
command in the client folder first. 66 | 67 | # Cross Platform 68 | 69 | ## Cordova 70 | 71 | In the client folder on level of the package.json type 72 | 73 | ``` 74 | npm run create:apps 75 | ``` 76 | 77 | ## Electron 78 | 79 | In the client folder on level of the package.json type 80 | 81 | ``` 82 | npm run create:desktop 83 | ``` 84 | 85 | ## Web 86 | 87 | In the client folder on level of the package.json type 88 | 89 | ``` 90 | npm run create:web 91 | ``` 92 | 93 | ## Web & Apps & Desktop 94 | 95 | In the client folder on level of the package.json type 96 | 97 | ``` 98 | npm run create:all 99 | ``` 100 | 101 | A `.dist`-Folder will be created containing all outputs. 102 | 103 | ![Alternativtext](.github/distFolder.png "DistFolder") 104 | 105 | #Screenshots 106 | 107 | ## Cordova-Project in Visual Studio 108 | ![Alternativtext](.github/cordova.png "Cordova") 109 | 110 | ## Windows 8.1 App 111 | ![Alternativtext](.github/win81nd.png "Windows81") 112 | 113 | ## Windows 10 Mobile (old design) 114 | ![Alternativtext](.github/winMobile.png "WinMobile") 115 | 116 | ## Android (with the new design) 117 | ![Alternativtext](.github/Android.png "Android") 118 | 119 | ## Windows 10 120 | ![Alternativtext](.github/win10nd.png "Windows10") 121 | 122 | ## Executable (Electron - with the new design) 123 | ![Alternativtext](.github/electronnd.png "Electron") 124 | 125 | ## Web 126 | ![Alternativtext](.github/webnd.png "Web") 127 | 128 | -------------------------------------------------------------------------------- /Server/FoodChooser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoodChooser", "FoodChooser\FoodChooser.csproj", "{45030DA0-AD1F-41D4-9E3C-EA968AE5A48B}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3DEA2BD4-3AB9-4966-B5DC-FCC15948258A}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {45030DA0-AD1F-41D4-9E3C-EA968AE5A48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {45030DA0-AD1F-41D4-9E3C-EA968AE5A48B}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {45030DA0-AD1F-41D4-9E3C-EA968AE5A48B}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {45030DA0-AD1F-41D4-9E3C-EA968AE5A48B}.Release|Any CPU.Build.0 = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(NestedProjects) = preSolution 25 | {45030DA0-AD1F-41D4-9E3C-EA968AE5A48B} = {3DEA2BD4-3AB9-4966-B5DC-FCC15948258A} 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {F1297F99-E6AF-434D-A300-3D0B103F016D} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /Server/FoodChooser/Configuration/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace FoodChooser.Configuration 7 | { 8 | public class AppSettings 9 | { 10 | public string DummyImageName { get; set; } = "dummy.png"; 11 | public string ImageSaveFolder { get; set; } = "FoodImages/"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Server/FoodChooser/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Models; 2 | using IdentityModel; 3 | using IdentityServer4.AccessTokenValidation; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Logging; 8 | using System.Collections.Generic; 9 | using System.Security.Claims; 10 | using System.Threading.Tasks; 11 | 12 | namespace FoodChooser.Controllers 13 | { 14 | [Route("api/[controller]")] 15 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Manage Accounts")] 16 | public class AccountController : Controller 17 | { 18 | private readonly UserManager _userManager; 19 | private readonly RoleManager _roleManager; 20 | private readonly SignInManager _signInManager; 21 | private readonly ILogger _logger; 22 | 23 | public AccountController( 24 | UserManager userManager, 25 | RoleManager roleManager, 26 | SignInManager signInManager, 27 | ILogger logger) 28 | { 29 | _userManager = userManager; 30 | _roleManager = roleManager; 31 | _signInManager = signInManager; 32 | _logger = logger; 33 | } 34 | 35 | /// 36 | /// Gets all the users. 37 | /// 38 | /// Returns all the users 39 | // GET api/identity/GetAll 40 | [HttpGet("GetAll")] 41 | public async Task GetAll() 42 | { 43 | var role = await _roleManager.FindByNameAsync("user"); 44 | var users = await _userManager.GetUsersInRoleAsync(role.Name); 45 | 46 | return new JsonResult(users); 47 | } 48 | 49 | /// 50 | /// Registers a new user. 51 | /// 52 | /// IdentityResult 53 | // POST: api/identity/Create 54 | [HttpPost("register")] 55 | [AllowAnonymous] 56 | public async Task Create([FromBody]RegisterBindingModel model) 57 | { 58 | if (model == null) 59 | { 60 | return BadRequest(); 61 | } 62 | 63 | if (!ModelState.IsValid) 64 | { 65 | return BadRequest(ModelState); 66 | } 67 | 68 | var user = new IdentityUser 69 | { 70 | AccessFailedCount = 0, 71 | Email = model.Email, 72 | EmailConfirmed = false, 73 | LockoutEnabled = true, 74 | NormalizedEmail = model.Email.ToUpper(), 75 | NormalizedUserName = model.Email.ToUpper(), 76 | TwoFactorEnabled = false, 77 | UserName = model.Username 78 | }; 79 | 80 | var result = await _userManager.CreateAsync(user, model.Password); 81 | 82 | if (result.Succeeded) 83 | { 84 | await addToRole(model.Username, "user"); 85 | await addClaims(model.Username); 86 | } 87 | 88 | return Ok(result); 89 | } 90 | 91 | private async Task addToRole(string userName, string roleName) 92 | { 93 | var user = await _userManager.FindByNameAsync(userName); 94 | await _userManager.AddToRoleAsync(user, roleName); 95 | } 96 | 97 | private async Task addClaims(string userName) 98 | { 99 | var user = await _userManager.FindByNameAsync(userName); 100 | var claims = new List { 101 | new Claim(type: JwtClaimTypes.Name, value: user.UserName) 102 | }; 103 | await _userManager.AddClaimsAsync(user, claims); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Server/FoodChooser/Controllers/FoodListsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using AutoMapper; 5 | using FoodChooser.Models; 6 | using FoodChooser.Repositories.List; 7 | using FoodChooser.ViewModels; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Identity; 11 | using Microsoft.AspNetCore.Hosting; 12 | using FoodChooser.Configuration; 13 | using Microsoft.Extensions.Options; 14 | using System.IO; 15 | using System.Collections.Generic; 16 | using FoodChooser.Helpers; 17 | using FoodChooser.Dtos; 18 | using IdentityServer4.AccessTokenValidation; 19 | 20 | namespace FoodChooser.Controllers 21 | { 22 | [Route("api/[controller]")] 23 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 24 | public class FoodListsController : Controller 25 | { 26 | private readonly IFoodListRepository _foodListRepository; 27 | private readonly IHostingEnvironment _hostingEnvironment; 28 | public AppSettings _appSettingsAccessor; 29 | const int MaxPageSize = 10; 30 | public UserManager _userManager; 31 | private readonly IUrlHelper _urlHelper; 32 | 33 | public FoodListsController(IFoodListRepository foodListRepository, 34 | UserManager userManager, 35 | IHostingEnvironment hostingEnvironment, 36 | IUrlHelper urlHelper, IOptions appSettingsAccessor) 37 | { 38 | _foodListRepository = foodListRepository; 39 | _userManager = userManager; 40 | _hostingEnvironment = hostingEnvironment; 41 | _appSettingsAccessor = appSettingsAccessor.Value; 42 | _urlHelper = urlHelper; 43 | } 44 | 45 | [HttpGet] 46 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 47 | public IActionResult GetAllLists([FromQuery] QueryParameters queryParameters) 48 | { 49 | var foodItems = _foodListRepository.GetAll(queryParameters) 50 | .Where(x => x.UserId == _userManager.GetUserId(HttpContext.User)); 51 | 52 | var allItemCount = _foodListRepository.Count(); 53 | 54 | var paginationMetadata = new 55 | { 56 | totalCount = allItemCount, 57 | pageSize = queryParameters.PageSize, 58 | currentPage = queryParameters.Page, 59 | totalPages = queryParameters.GetTotalPages(allItemCount) 60 | }; 61 | 62 | Response.Headers.Add("X-Pagination", 63 | Newtonsoft.Json.JsonConvert.SerializeObject(paginationMetadata)); 64 | 65 | var links = CreateLinksForCollection(queryParameters, allItemCount); 66 | 67 | var toReturn = foodItems.Select(x => ExpandSingleFoodItem(x)); 68 | 69 | return Ok(new 70 | { 71 | value = toReturn, 72 | links = links 73 | }); 74 | } 75 | 76 | [HttpGet] 77 | [Route("{id}", Name = nameof(GetSingleList))] 78 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 79 | public IActionResult GetSingleList(Guid id) 80 | { 81 | FoodList singleFoodList = _foodListRepository.GetSingle(id, false); 82 | 83 | if (singleFoodList == null) 84 | { 85 | return NotFound(); 86 | } 87 | 88 | if (singleFoodList.UserId != _userManager.GetUserId(HttpContext.User)) 89 | { 90 | return new StatusCodeResult((int)HttpStatusCode.Forbidden); 91 | } 92 | 93 | 94 | var toReturn = Mapper.Map(singleFoodList); 95 | 96 | return Ok(toReturn); 97 | } 98 | 99 | [HttpGet] 100 | [Route("{id}/getrandomimage")] 101 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 102 | public IActionResult GetRandomImageStringFromList(Guid id) 103 | { 104 | FoodList singleFoodList = _foodListRepository.GetSingle(id, true); 105 | 106 | if (singleFoodList == null) 107 | { 108 | return NotFound(); 109 | } 110 | 111 | if (singleFoodList.UserId != _userManager.GetUserId(HttpContext.User)) 112 | { 113 | return new StatusCodeResult((int)HttpStatusCode.Forbidden); 114 | } 115 | 116 | if (!singleFoodList.Foods.Any()) 117 | { 118 | string imagePath = Path.Combine(_hostingEnvironment.ContentRootPath, _appSettingsAccessor.ImageSaveFolder, _appSettingsAccessor.DummyImageName); 119 | return Ok(imagePath); 120 | } 121 | 122 | Random random = new Random(); 123 | int index = random.Next(0, singleFoodList.Foods.Count); 124 | FoodItem foodItem = singleFoodList.Foods.ToList()[index]; 125 | 126 | if (String.IsNullOrEmpty(foodItem.ImageString)) 127 | { 128 | string imagePath = Path.Combine(_hostingEnvironment.ContentRootPath, _appSettingsAccessor.ImageSaveFolder, _appSettingsAccessor.DummyImageName); 129 | return Ok(imagePath); 130 | } 131 | 132 | FoodItemDto foodItemViewModel = Mapper.Map(foodItem); 133 | return Ok(foodItemViewModel.ImageString); 134 | 135 | } 136 | 137 | [HttpPost(Name = nameof(AddList))] 138 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Modify Resources")] 139 | public IActionResult AddList([FromBody] FoodListDto viewModel) 140 | { 141 | if (viewModel == null) 142 | { 143 | return BadRequest(); 144 | } 145 | 146 | if (!ModelState.IsValid) 147 | { 148 | return BadRequest(ModelState); 149 | } 150 | 151 | FoodList item = Mapper.Map(viewModel); 152 | item.UserId = _userManager.GetUserId(HttpContext.User); 153 | _foodListRepository.Add(item); 154 | 155 | if (_foodListRepository.Save()) 156 | { 157 | return CreatedAtRoute("GetSingleList", new { id = item.Id }, item); 158 | } 159 | 160 | return BadRequest(); 161 | } 162 | 163 | [HttpDelete] 164 | [Route("{id}")] 165 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Modify Resources")] 166 | public IActionResult DeleteList(Guid id) 167 | { 168 | FoodList singleFoodList = _foodListRepository.GetSingle(id, true); 169 | 170 | if (singleFoodList == null) 171 | { 172 | return NotFound(); 173 | } 174 | 175 | if (singleFoodList.UserId != _userManager.GetUserId(HttpContext.User)) 176 | { 177 | return new StatusCodeResult((int)HttpStatusCode.Forbidden); 178 | } 179 | 180 | _foodListRepository.Delete(singleFoodList.Id); 181 | 182 | if (_foodListRepository.Save()) 183 | { 184 | return NoContent(); 185 | } 186 | 187 | return BadRequest(); 188 | 189 | } 190 | 191 | private List CreateLinksForCollection(QueryParameters queryParameters, int totalCount) 192 | { 193 | var links = new List(); 194 | 195 | // self 196 | links.Add( 197 | new LinkDto(_urlHelper.Link(nameof(GetAllLists), new 198 | { 199 | pagecount = queryParameters.PageSize, 200 | page = queryParameters.Page 201 | }), "self", "GET")); 202 | 203 | links.Add(new LinkDto(_urlHelper.Link(nameof(GetAllLists), new 204 | { 205 | pagecount = queryParameters.PageSize, 206 | page = 1 207 | }), "first", "GET")); 208 | 209 | links.Add(new LinkDto(_urlHelper.Link(nameof(GetAllLists), new 210 | { 211 | pagecount = queryParameters.PageSize, 212 | page = queryParameters.GetTotalPages(totalCount) 213 | }), "last", "GET")); 214 | 215 | if (queryParameters.HasNext(totalCount)) 216 | { 217 | links.Add(new LinkDto(_urlHelper.Link(nameof(GetAllLists), new 218 | { 219 | pagecount = queryParameters.PageSize, 220 | page = queryParameters.Page + 1 221 | }), "next", "GET")); 222 | } 223 | 224 | if (queryParameters.HasPrevious()) 225 | { 226 | links.Add(new LinkDto(_urlHelper.Link(nameof(GetAllLists), new 227 | { 228 | pagecount = queryParameters.PageSize, 229 | page = queryParameters.Page - 1 230 | }), "previous", "GET")); 231 | } 232 | 233 | return links; 234 | } 235 | 236 | private dynamic ExpandSingleFoodItem(FoodList foodList) 237 | { 238 | var links = GetLinks(foodList.Id); 239 | FoodListDto item = Mapper.Map(foodList); 240 | 241 | var resourceToReturn = item.ToDynamic() as IDictionary; 242 | resourceToReturn.Add("links", links); 243 | 244 | return resourceToReturn; 245 | } 246 | 247 | private IEnumerable GetLinks(Guid id) 248 | { 249 | var links = new List(); 250 | 251 | links.Add( 252 | new LinkDto(_urlHelper.Link(nameof(GetSingleList), new { id = id }), 253 | "self", 254 | "GET")); 255 | 256 | links.Add( 257 | new LinkDto(_urlHelper.Link(nameof(DeleteList), new { id = id }), 258 | "delete_food", 259 | "DELETE")); 260 | 261 | links.Add( 262 | new LinkDto(_urlHelper.Link(nameof(AddList), null), 263 | "create_food", 264 | "POST")); 265 | 266 | //links.Add( 267 | // new LinkDto(_urlHelper.Link(nameof(), new { id = id }), 268 | // "update_food", 269 | // "PUT")); 270 | 271 | return links; 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Server/FoodChooser/Controllers/FoodsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using AutoMapper; 7 | using FoodChooser.Models; 8 | using FoodChooser.Repositories.Food; 9 | using FoodChooser.Repositories.List; 10 | using FoodChooser.Services; 11 | using FoodChooser.ViewModels; 12 | using Microsoft.AspNetCore.Mvc; 13 | using Microsoft.AspNetCore.Authorization; 14 | using Microsoft.AspNetCore.Identity; 15 | using FoodChooser.Configuration; 16 | using Microsoft.Extensions.Options; 17 | using Microsoft.AspNetCore.Hosting; 18 | using IdentityServer4.AccessTokenValidation; 19 | 20 | namespace FoodChooser.Controllers 21 | { 22 | [Route("api")] 23 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 24 | public class FoodsController : Controller 25 | { 26 | private static string DataImagePngBase64Prefix = "data:image/png;base64"; 27 | 28 | const int MaxPageSize = 10; 29 | private readonly IFoodRepository _foodRepository; 30 | private readonly IFoodListRepository _foodListRepository; 31 | private readonly IRandomNumberGenerator _randomNumberGenerator; 32 | private readonly UserManager _userManager; 33 | private readonly AppSettings _appSettingsAccessor; 34 | private readonly IHostingEnvironment _hostingEnvironment; 35 | 36 | 37 | public FoodsController(IFoodRepository foodRepository, IFoodListRepository foodListRepository, 38 | IRandomNumberGenerator randomNumberGenerator, UserManager userManager, 39 | IOptions appSettingsAccessor, IHostingEnvironment hostingEnvironment) 40 | { 41 | _foodRepository = foodRepository; 42 | _foodListRepository = foodListRepository; 43 | _randomNumberGenerator = randomNumberGenerator; 44 | _userManager = userManager; 45 | _appSettingsAccessor = appSettingsAccessor.Value; 46 | _hostingEnvironment = hostingEnvironment; 47 | } 48 | 49 | [HttpGet] 50 | [Route("foodlists/{id}/foods")] 51 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 52 | public IActionResult GetFoodsFromList(Guid id) 53 | { 54 | FoodList foodList = _foodListRepository.GetSingle(id, true); 55 | 56 | if (foodList.Foods == null) 57 | { 58 | var items = new List(); 59 | return Ok(items.Select(x => Mapper.Map(x))); 60 | } 61 | 62 | return Ok(foodList.Foods.Select(x => Mapper.Map(x))); 63 | } 64 | 65 | [HttpGet] 66 | [Route("foodlists/{listId}/food/{foodItemId}")] 67 | [Route("foods/{foodItemId}", Name = "GetSingleFood")] 68 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Access Resources")] 69 | public IActionResult GetSingleFood(Guid foodItemId, Guid? listId = null) 70 | { 71 | FoodItem foodItem; 72 | if (listId.HasValue) 73 | { 74 | foodItem = _foodRepository.GetSingle(foodItemId, listId.Value); 75 | } 76 | else 77 | { 78 | foodItem = _foodRepository.GetSingle(foodItemId, null); 79 | } 80 | 81 | if (foodItem == null) 82 | { 83 | return NotFound(); 84 | } 85 | 86 | if (foodItem.FoodList == null || foodItem.FoodList.UserId != _userManager.GetUserId(HttpContext.User)) 87 | { 88 | return new StatusCodeResult((int)HttpStatusCode.Forbidden); 89 | } 90 | 91 | return Ok(Mapper.Map(foodItem)); 92 | } 93 | 94 | [HttpGet] 95 | [AllowAnonymous] 96 | [Route("foods/getrandomfood")] 97 | public IActionResult GetRandomFood() 98 | { 99 | IEnumerable foodItems = _foodRepository.GetAllPublic(); 100 | 101 | if (!foodItems.Any()) 102 | { 103 | return BadRequest("No Items Found"); 104 | } 105 | 106 | FoodItem elementAt = foodItems.ElementAt(_randomNumberGenerator.GetRandomNumber(foodItems.Count())); 107 | 108 | if (elementAt == null) 109 | { 110 | return NotFound(); 111 | } 112 | 113 | return Ok(Mapper.Map(elementAt)); 114 | } 115 | 116 | [HttpPost] 117 | [Route("foods")] 118 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Modify Resources")] 119 | public IActionResult AddFoodToList([FromBody]FoodItemDto viewModel) 120 | { 121 | if (viewModel == null) 122 | { 123 | return BadRequest(); 124 | } 125 | 126 | if (!ModelState.IsValid) 127 | { 128 | return BadRequest(ModelState); 129 | } 130 | 131 | FoodList singleFoodList = _foodListRepository.GetSingle(viewModel.FoodListId, true); 132 | FoodItem item = Mapper.Map(viewModel); 133 | item.Created = DateTime.Now; 134 | item.ImageString = _appSettingsAccessor.DummyImageName; 135 | singleFoodList.Foods.Add(item); 136 | _foodListRepository.Update(singleFoodList); 137 | 138 | if (_foodListRepository.Save()) 139 | { 140 | return CreatedAtRoute("GetSingleFood", new { foodItemId = item.Id }, Mapper.Map(item)); 141 | } 142 | 143 | return BadRequest(); 144 | } 145 | 146 | [HttpPut] 147 | [Route("foods/{foodItemId}")] 148 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Modify Resources")] 149 | public IActionResult UpdateFoodInList(Guid foodItemId, [FromBody]FoodItemDto viewModel) 150 | { 151 | if (viewModel == null) 152 | { 153 | return BadRequest(); 154 | } 155 | 156 | if (!ModelState.IsValid) 157 | { 158 | return BadRequest(ModelState); 159 | } 160 | 161 | FoodItem singleById = _foodRepository.GetSingle(foodItemId, null); 162 | 163 | if (singleById == null) 164 | { 165 | return NotFound(); 166 | } 167 | 168 | singleById.ItemName = viewModel.ItemName; 169 | singleById.IsPublic = viewModel.IsPublic; 170 | 171 | if (ImageIsNewImage(viewModel)) 172 | { 173 | HandleImage(viewModel, singleById); 174 | } 175 | 176 | _foodRepository.Update(singleById); 177 | 178 | if (_foodListRepository.Save()) 179 | { 180 | return Ok(Mapper.Map(singleById)); 181 | } 182 | 183 | return BadRequest(); 184 | } 185 | 186 | [HttpDelete] 187 | [Route("foods/{foodItemId}")] 188 | [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = "Modify Resources")] 189 | public IActionResult DeleteFoodFromList(Guid foodItemId) 190 | { 191 | FoodItem singleById = _foodRepository.GetSingle(foodItemId, null); 192 | 193 | if (singleById == null) 194 | { 195 | return NotFound(); 196 | } 197 | 198 | _foodRepository.Delete(foodItemId); 199 | if (_foodListRepository.Save()) 200 | { 201 | return NoContent(); 202 | } 203 | 204 | return BadRequest(); 205 | } 206 | 207 | private string SaveImage(FoodItemDto viewModel) 208 | { 209 | if (String.IsNullOrEmpty(viewModel.ImageString)) 210 | { 211 | return String.Empty; 212 | } 213 | 214 | string filePath = Path.Combine(_hostingEnvironment.WebRootPath, _appSettingsAccessor.ImageSaveFolder); 215 | 216 | //Check if directory exist 217 | if (!System.IO.Directory.Exists(filePath)) 218 | { 219 | System.IO.Directory.CreateDirectory(filePath); //Create directory if it doesn't exist 220 | } 221 | 222 | // Get Filename of new image 223 | var newFileName = Guid.NewGuid() + ".png"; 224 | 225 | //set the image path 226 | string imgPath = Path.Combine(filePath, newFileName); 227 | 228 | byte[] imageBytes = Convert.FromBase64String(viewModel.ImageString.Split(',')[1]); 229 | 230 | System.IO.File.WriteAllBytes(imgPath, imageBytes); 231 | 232 | return newFileName; 233 | } 234 | 235 | private static bool ImageIsNewImage(FoodItemDto viewModel) 236 | { 237 | return viewModel.ImageString.StartsWith(DataImagePngBase64Prefix); 238 | } 239 | 240 | 241 | private void HandleImage(FoodItemDto viewModel, FoodItem singleById) 242 | { 243 | // save new image 244 | var newFileName = SaveImage(viewModel); 245 | 246 | if (!String.IsNullOrEmpty(newFileName)) 247 | { 248 | if (singleById.ImageString != _appSettingsAccessor.DummyImageName) 249 | { 250 | // if old image is there 251 | var oldimagePath = Path.Combine(_hostingEnvironment.WebRootPath, _appSettingsAccessor.ImageSaveFolder, singleById.ImageString); 252 | 253 | // delete old image 254 | if (System.IO.File.Exists(oldimagePath)) 255 | { 256 | System.IO.File.Delete(oldimagePath); 257 | } 258 | } 259 | 260 | // save db entry 261 | singleById.ImageString = newFileName; 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Server/FoodChooser/Dtos/FoodItemDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace FoodChooser.ViewModels 5 | { 6 | public class FoodItemDto 7 | { 8 | public Guid Id { get; set; } 9 | [Required] 10 | public string ItemName { get; set; } 11 | public int Rating{ get; set; } 12 | public Guid FoodListId { get; set; } 13 | public DateTime Created { get; set; } 14 | public bool IsPublic { get; set; } 15 | public string ImageString { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/FoodChooser/Dtos/FoodListDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using FoodChooser.Models; 4 | using System; 5 | 6 | namespace FoodChooser.ViewModels 7 | { 8 | public class FoodListDto 9 | { 10 | public Guid Id { get; set; } 11 | [Required] 12 | public string Name { get; set; } 13 | public string UserId { get; set; } 14 | public ICollection Foods { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Server/FoodChooser/Dtos/LinkDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace FoodChooser.Dtos 7 | { 8 | public class LinkDto 9 | { 10 | public string Href { get; set; } 11 | public string Rel { get; set; } 12 | public string Method { get; set; } 13 | 14 | public LinkDto(string href, string rel, string method) 15 | { 16 | Href = href; 17 | Rel = rel; 18 | Method = method; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/FoodChooser/Dtos/SharedFoodListDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FoodChooser.ViewModels 4 | { 5 | public class SharedFoodListDto 6 | { 7 | public int Id { get; set; } 8 | public int FoodListId { get; set; } 9 | public virtual List FoodList { get; set; } 10 | public string UserId { get; set; } 11 | public string UserEmail { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Server/FoodChooser/FoodChooser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Server/FoodChooser/FoodChooser.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ShowAllFiles 5 | FoodChooser - Web Deploy 6 | 1.8 7 | true 8 | enabled 9 | disabled 10 | false 11 | FoodChooser 12 | 13 | 14 | ProjectDebugger 15 | 16 | 17 | 18 | 19 | 20 | webapp/index.html 21 | CurrentPage 22 | True 23 | False 24 | False 25 | False 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | True 35 | True 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Server/FoodChooser/Helpers/DynamicExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using System.Dynamic; 4 | 5 | namespace FoodChooser.Helpers 6 | { 7 | public static class DynamicExtensions 8 | { 9 | public static dynamic ToDynamic(this object value) 10 | { 11 | IDictionary expando = new ExpandoObject(); 12 | 13 | foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType())) 14 | expando.Add(property.Name, property.GetValue(value)); 15 | 16 | return expando as ExpandoObject; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Server/FoodChooser/Helpers/QueryParametersExtensions.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Models; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace FoodChooser.Helpers 6 | { 7 | public static class QueryParametersExtensions 8 | { 9 | public static bool HasPrevious(this QueryParameters queryParameters) 10 | { 11 | return (queryParameters.Page > 1); 12 | } 13 | 14 | public static bool HasNext(this QueryParameters queryParameters, int totalCount) 15 | { 16 | return (queryParameters.Page < (int)GetTotalPages(queryParameters, totalCount)); 17 | } 18 | 19 | public static double GetTotalPages(this QueryParameters queryParameters, int totalCount) 20 | { 21 | return Math.Ceiling(totalCount / (double)queryParameters.PageSize); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Server/FoodChooser/IdentityConfig.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4; 2 | using IdentityServer4.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace FoodChooser 9 | { 10 | public class IdentityConfig 11 | { 12 | // Identity resources (used by UserInfo endpoint). 13 | public static IEnumerable GetIdentityResources() 14 | { 15 | return new List 16 | { 17 | new IdentityResources.OpenId(), 18 | new IdentityResources.Profile(), 19 | new IdentityResource("roles", new List { "role" }) 20 | }; 21 | } 22 | 23 | // Api resources. 24 | public static IEnumerable GetApiResources() 25 | { 26 | return new List 27 | { 28 | new ApiResource("WebAPI" ) { 29 | UserClaims = { "role" } 30 | } 31 | }; 32 | } 33 | 34 | // Clients want to access resources. 35 | public static IEnumerable GetClients() 36 | { 37 | // Clients credentials. 38 | return new List 39 | { 40 | // http://docs.identityserver.io/en/dev/reference/client.html. 41 | new Client 42 | { 43 | ClientId = "AngularFoodClient", 44 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant. 45 | AllowAccessTokensViaBrowser = true, 46 | RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint. 47 | 48 | AccessTokenLifetime = 900, // Lifetime of access token in seconds. 49 | 50 | AllowedScopes = { 51 | IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint. 52 | IdentityServerConstants.StandardScopes.Profile, 53 | "roles", 54 | "WebAPI" 55 | }, 56 | AllowOfflineAccess = true // For refresh token. 57 | } 58 | }; 59 | 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Server/FoodChooser/Migrations/20171025161811_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using FoodChooser.Repositories; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace FoodChooser.Migrations 12 | { 13 | [DbContext(typeof(FoodChooserDbContext))] 14 | [Migration("20171025161811_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("FoodChooser.Models.FoodItem", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("Created"); 30 | 31 | b.Property("FoodListId"); 32 | 33 | b.Property("ImageString"); 34 | 35 | b.Property("IsPublic"); 36 | 37 | b.Property("ItemName"); 38 | 39 | b.HasKey("Id"); 40 | 41 | b.HasIndex("FoodListId"); 42 | 43 | b.ToTable("FoodItems"); 44 | }); 45 | 46 | modelBuilder.Entity("FoodChooser.Models.FoodList", b => 47 | { 48 | b.Property("Id") 49 | .ValueGeneratedOnAdd(); 50 | 51 | b.Property("Name"); 52 | 53 | b.Property("UserId"); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.ToTable("FoodLists"); 58 | }); 59 | 60 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 61 | { 62 | b.Property("Id") 63 | .ValueGeneratedOnAdd(); 64 | 65 | b.Property("ConcurrencyStamp") 66 | .IsConcurrencyToken(); 67 | 68 | b.Property("Name") 69 | .HasMaxLength(256); 70 | 71 | b.Property("NormalizedName") 72 | .HasMaxLength(256); 73 | 74 | b.HasKey("Id"); 75 | 76 | b.HasIndex("NormalizedName") 77 | .IsUnique() 78 | .HasName("RoleNameIndex") 79 | .HasFilter("[NormalizedName] IS NOT NULL"); 80 | 81 | b.ToTable("AspNetRoles"); 82 | }); 83 | 84 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 85 | { 86 | b.Property("Id") 87 | .ValueGeneratedOnAdd(); 88 | 89 | b.Property("ClaimType"); 90 | 91 | b.Property("ClaimValue"); 92 | 93 | b.Property("RoleId") 94 | .IsRequired(); 95 | 96 | b.HasKey("Id"); 97 | 98 | b.HasIndex("RoleId"); 99 | 100 | b.ToTable("AspNetRoleClaims"); 101 | }); 102 | 103 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 104 | { 105 | b.Property("Id") 106 | .ValueGeneratedOnAdd(); 107 | 108 | b.Property("AccessFailedCount"); 109 | 110 | b.Property("ConcurrencyStamp") 111 | .IsConcurrencyToken(); 112 | 113 | b.Property("Email") 114 | .HasMaxLength(256); 115 | 116 | b.Property("EmailConfirmed"); 117 | 118 | b.Property("LockoutEnabled"); 119 | 120 | b.Property("LockoutEnd"); 121 | 122 | b.Property("NormalizedEmail") 123 | .HasMaxLength(256); 124 | 125 | b.Property("NormalizedUserName") 126 | .HasMaxLength(256); 127 | 128 | b.Property("PasswordHash"); 129 | 130 | b.Property("PhoneNumber"); 131 | 132 | b.Property("PhoneNumberConfirmed"); 133 | 134 | b.Property("SecurityStamp"); 135 | 136 | b.Property("TwoFactorEnabled"); 137 | 138 | b.Property("UserName") 139 | .HasMaxLength(256); 140 | 141 | b.HasKey("Id"); 142 | 143 | b.HasIndex("NormalizedEmail") 144 | .HasName("EmailIndex"); 145 | 146 | b.HasIndex("NormalizedUserName") 147 | .IsUnique() 148 | .HasName("UserNameIndex") 149 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 150 | 151 | b.ToTable("AspNetUsers"); 152 | }); 153 | 154 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 155 | { 156 | b.Property("Id") 157 | .ValueGeneratedOnAdd(); 158 | 159 | b.Property("ClaimType"); 160 | 161 | b.Property("ClaimValue"); 162 | 163 | b.Property("UserId") 164 | .IsRequired(); 165 | 166 | b.HasKey("Id"); 167 | 168 | b.HasIndex("UserId"); 169 | 170 | b.ToTable("AspNetUserClaims"); 171 | }); 172 | 173 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 174 | { 175 | b.Property("LoginProvider"); 176 | 177 | b.Property("ProviderKey"); 178 | 179 | b.Property("ProviderDisplayName"); 180 | 181 | b.Property("UserId") 182 | .IsRequired(); 183 | 184 | b.HasKey("LoginProvider", "ProviderKey"); 185 | 186 | b.HasIndex("UserId"); 187 | 188 | b.ToTable("AspNetUserLogins"); 189 | }); 190 | 191 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 192 | { 193 | b.Property("UserId"); 194 | 195 | b.Property("RoleId"); 196 | 197 | b.HasKey("UserId", "RoleId"); 198 | 199 | b.HasIndex("RoleId"); 200 | 201 | b.ToTable("AspNetUserRoles"); 202 | }); 203 | 204 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 205 | { 206 | b.Property("UserId"); 207 | 208 | b.Property("LoginProvider"); 209 | 210 | b.Property("Name"); 211 | 212 | b.Property("Value"); 213 | 214 | b.HasKey("UserId", "LoginProvider", "Name"); 215 | 216 | b.ToTable("AspNetUserTokens"); 217 | }); 218 | 219 | modelBuilder.Entity("FoodChooser.Models.FoodItem", b => 220 | { 221 | b.HasOne("FoodChooser.Models.FoodList", "FoodList") 222 | .WithMany("Foods") 223 | .HasForeignKey("FoodListId") 224 | .OnDelete(DeleteBehavior.Cascade); 225 | }); 226 | 227 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 228 | { 229 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 230 | .WithMany() 231 | .HasForeignKey("RoleId") 232 | .OnDelete(DeleteBehavior.Cascade); 233 | }); 234 | 235 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 236 | { 237 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 238 | .WithMany() 239 | .HasForeignKey("UserId") 240 | .OnDelete(DeleteBehavior.Cascade); 241 | }); 242 | 243 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 244 | { 245 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 246 | .WithMany() 247 | .HasForeignKey("UserId") 248 | .OnDelete(DeleteBehavior.Cascade); 249 | }); 250 | 251 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 252 | { 253 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 254 | .WithMany() 255 | .HasForeignKey("RoleId") 256 | .OnDelete(DeleteBehavior.Cascade); 257 | 258 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 259 | .WithMany() 260 | .HasForeignKey("UserId") 261 | .OnDelete(DeleteBehavior.Cascade); 262 | }); 263 | 264 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 265 | { 266 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 267 | .WithMany() 268 | .HasForeignKey("UserId") 269 | .OnDelete(DeleteBehavior.Cascade); 270 | }); 271 | #pragma warning restore 612, 618 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Server/FoodChooser/Migrations/20171025161811_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace FoodChooser.Migrations 7 | { 8 | public partial class InitialCreate : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "AspNetRoles", 14 | columns: table => new 15 | { 16 | Id = table.Column(type: "nvarchar(450)", nullable: false), 17 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 18 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 19 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "AspNetUsers", 28 | columns: table => new 29 | { 30 | Id = table.Column(type: "nvarchar(450)", nullable: false), 31 | AccessFailedCount = table.Column(type: "int", nullable: false), 32 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 33 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 34 | EmailConfirmed = table.Column(type: "bit", nullable: false), 35 | LockoutEnabled = table.Column(type: "bit", nullable: false), 36 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), 37 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 38 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 39 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), 40 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), 41 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), 42 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), 43 | TwoFactorEnabled = table.Column(type: "bit", nullable: false), 44 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) 45 | }, 46 | constraints: table => 47 | { 48 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 49 | }); 50 | 51 | migrationBuilder.CreateTable( 52 | name: "FoodLists", 53 | columns: table => new 54 | { 55 | Id = table.Column(type: "uniqueidentifier", nullable: false), 56 | Name = table.Column(type: "nvarchar(max)", nullable: true), 57 | UserId = table.Column(type: "nvarchar(max)", nullable: true) 58 | }, 59 | constraints: table => 60 | { 61 | table.PrimaryKey("PK_FoodLists", x => x.Id); 62 | }); 63 | 64 | migrationBuilder.CreateTable( 65 | name: "AspNetRoleClaims", 66 | columns: table => new 67 | { 68 | Id = table.Column(type: "int", nullable: false) 69 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 70 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 71 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), 72 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 73 | }, 74 | constraints: table => 75 | { 76 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 77 | table.ForeignKey( 78 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 79 | column: x => x.RoleId, 80 | principalTable: "AspNetRoles", 81 | principalColumn: "Id", 82 | onDelete: ReferentialAction.Cascade); 83 | }); 84 | 85 | migrationBuilder.CreateTable( 86 | name: "AspNetUserClaims", 87 | columns: table => new 88 | { 89 | Id = table.Column(type: "int", nullable: false) 90 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 91 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 92 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), 93 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 94 | }, 95 | constraints: table => 96 | { 97 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 98 | table.ForeignKey( 99 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 100 | column: x => x.UserId, 101 | principalTable: "AspNetUsers", 102 | principalColumn: "Id", 103 | onDelete: ReferentialAction.Cascade); 104 | }); 105 | 106 | migrationBuilder.CreateTable( 107 | name: "AspNetUserLogins", 108 | columns: table => new 109 | { 110 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 111 | ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), 112 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), 113 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 114 | }, 115 | constraints: table => 116 | { 117 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 118 | table.ForeignKey( 119 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 120 | column: x => x.UserId, 121 | principalTable: "AspNetUsers", 122 | principalColumn: "Id", 123 | onDelete: ReferentialAction.Cascade); 124 | }); 125 | 126 | migrationBuilder.CreateTable( 127 | name: "AspNetUserRoles", 128 | columns: table => new 129 | { 130 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 131 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 132 | }, 133 | constraints: table => 134 | { 135 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 136 | table.ForeignKey( 137 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 138 | column: x => x.RoleId, 139 | principalTable: "AspNetRoles", 140 | principalColumn: "Id", 141 | onDelete: ReferentialAction.Cascade); 142 | table.ForeignKey( 143 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 144 | column: x => x.UserId, 145 | principalTable: "AspNetUsers", 146 | principalColumn: "Id", 147 | onDelete: ReferentialAction.Cascade); 148 | }); 149 | 150 | migrationBuilder.CreateTable( 151 | name: "AspNetUserTokens", 152 | columns: table => new 153 | { 154 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 155 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 156 | Name = table.Column(type: "nvarchar(450)", nullable: false), 157 | Value = table.Column(type: "nvarchar(max)", nullable: true) 158 | }, 159 | constraints: table => 160 | { 161 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 162 | table.ForeignKey( 163 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 164 | column: x => x.UserId, 165 | principalTable: "AspNetUsers", 166 | principalColumn: "Id", 167 | onDelete: ReferentialAction.Cascade); 168 | }); 169 | 170 | migrationBuilder.CreateTable( 171 | name: "FoodItems", 172 | columns: table => new 173 | { 174 | Id = table.Column(type: "uniqueidentifier", nullable: false), 175 | Created = table.Column(type: "datetime2", nullable: false), 176 | FoodListId = table.Column(type: "uniqueidentifier", nullable: false), 177 | ImageString = table.Column(type: "nvarchar(max)", nullable: true), 178 | IsPublic = table.Column(type: "bit", nullable: false), 179 | ItemName = table.Column(type: "nvarchar(max)", nullable: true) 180 | }, 181 | constraints: table => 182 | { 183 | table.PrimaryKey("PK_FoodItems", x => x.Id); 184 | table.ForeignKey( 185 | name: "FK_FoodItems_FoodLists_FoodListId", 186 | column: x => x.FoodListId, 187 | principalTable: "FoodLists", 188 | principalColumn: "Id", 189 | onDelete: ReferentialAction.Cascade); 190 | }); 191 | 192 | migrationBuilder.CreateIndex( 193 | name: "IX_AspNetRoleClaims_RoleId", 194 | table: "AspNetRoleClaims", 195 | column: "RoleId"); 196 | 197 | migrationBuilder.CreateIndex( 198 | name: "RoleNameIndex", 199 | table: "AspNetRoles", 200 | column: "NormalizedName", 201 | unique: true, 202 | filter: "[NormalizedName] IS NOT NULL"); 203 | 204 | migrationBuilder.CreateIndex( 205 | name: "IX_AspNetUserClaims_UserId", 206 | table: "AspNetUserClaims", 207 | column: "UserId"); 208 | 209 | migrationBuilder.CreateIndex( 210 | name: "IX_AspNetUserLogins_UserId", 211 | table: "AspNetUserLogins", 212 | column: "UserId"); 213 | 214 | migrationBuilder.CreateIndex( 215 | name: "IX_AspNetUserRoles_RoleId", 216 | table: "AspNetUserRoles", 217 | column: "RoleId"); 218 | 219 | migrationBuilder.CreateIndex( 220 | name: "EmailIndex", 221 | table: "AspNetUsers", 222 | column: "NormalizedEmail"); 223 | 224 | migrationBuilder.CreateIndex( 225 | name: "UserNameIndex", 226 | table: "AspNetUsers", 227 | column: "NormalizedUserName", 228 | unique: true, 229 | filter: "[NormalizedUserName] IS NOT NULL"); 230 | 231 | migrationBuilder.CreateIndex( 232 | name: "IX_FoodItems_FoodListId", 233 | table: "FoodItems", 234 | column: "FoodListId"); 235 | } 236 | 237 | protected override void Down(MigrationBuilder migrationBuilder) 238 | { 239 | migrationBuilder.DropTable( 240 | name: "AspNetRoleClaims"); 241 | 242 | migrationBuilder.DropTable( 243 | name: "AspNetUserClaims"); 244 | 245 | migrationBuilder.DropTable( 246 | name: "AspNetUserLogins"); 247 | 248 | migrationBuilder.DropTable( 249 | name: "AspNetUserRoles"); 250 | 251 | migrationBuilder.DropTable( 252 | name: "AspNetUserTokens"); 253 | 254 | migrationBuilder.DropTable( 255 | name: "FoodItems"); 256 | 257 | migrationBuilder.DropTable( 258 | name: "AspNetRoles"); 259 | 260 | migrationBuilder.DropTable( 261 | name: "AspNetUsers"); 262 | 263 | migrationBuilder.DropTable( 264 | name: "FoodLists"); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Server/FoodChooser/Migrations/FoodChooserDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using FoodChooser.Repositories; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace FoodChooser.Migrations 12 | { 13 | [DbContext(typeof(FoodChooserDbContext))] 14 | partial class FoodChooserDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("FoodChooser.Models.FoodItem", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("Created"); 29 | 30 | b.Property("FoodListId"); 31 | 32 | b.Property("ImageString"); 33 | 34 | b.Property("IsPublic"); 35 | 36 | b.Property("ItemName"); 37 | 38 | b.HasKey("Id"); 39 | 40 | b.HasIndex("FoodListId"); 41 | 42 | b.ToTable("FoodItems"); 43 | }); 44 | 45 | modelBuilder.Entity("FoodChooser.Models.FoodList", b => 46 | { 47 | b.Property("Id") 48 | .ValueGeneratedOnAdd(); 49 | 50 | b.Property("Name"); 51 | 52 | b.Property("UserId"); 53 | 54 | b.HasKey("Id"); 55 | 56 | b.ToTable("FoodLists"); 57 | }); 58 | 59 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 60 | { 61 | b.Property("Id") 62 | .ValueGeneratedOnAdd(); 63 | 64 | b.Property("ConcurrencyStamp") 65 | .IsConcurrencyToken(); 66 | 67 | b.Property("Name") 68 | .HasMaxLength(256); 69 | 70 | b.Property("NormalizedName") 71 | .HasMaxLength(256); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("NormalizedName") 76 | .IsUnique() 77 | .HasName("RoleNameIndex") 78 | .HasFilter("[NormalizedName] IS NOT NULL"); 79 | 80 | b.ToTable("AspNetRoles"); 81 | }); 82 | 83 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 84 | { 85 | b.Property("Id") 86 | .ValueGeneratedOnAdd(); 87 | 88 | b.Property("ClaimType"); 89 | 90 | b.Property("ClaimValue"); 91 | 92 | b.Property("RoleId") 93 | .IsRequired(); 94 | 95 | b.HasKey("Id"); 96 | 97 | b.HasIndex("RoleId"); 98 | 99 | b.ToTable("AspNetRoleClaims"); 100 | }); 101 | 102 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 103 | { 104 | b.Property("Id") 105 | .ValueGeneratedOnAdd(); 106 | 107 | b.Property("AccessFailedCount"); 108 | 109 | b.Property("ConcurrencyStamp") 110 | .IsConcurrencyToken(); 111 | 112 | b.Property("Email") 113 | .HasMaxLength(256); 114 | 115 | b.Property("EmailConfirmed"); 116 | 117 | b.Property("LockoutEnabled"); 118 | 119 | b.Property("LockoutEnd"); 120 | 121 | b.Property("NormalizedEmail") 122 | .HasMaxLength(256); 123 | 124 | b.Property("NormalizedUserName") 125 | .HasMaxLength(256); 126 | 127 | b.Property("PasswordHash"); 128 | 129 | b.Property("PhoneNumber"); 130 | 131 | b.Property("PhoneNumberConfirmed"); 132 | 133 | b.Property("SecurityStamp"); 134 | 135 | b.Property("TwoFactorEnabled"); 136 | 137 | b.Property("UserName") 138 | .HasMaxLength(256); 139 | 140 | b.HasKey("Id"); 141 | 142 | b.HasIndex("NormalizedEmail") 143 | .HasName("EmailIndex"); 144 | 145 | b.HasIndex("NormalizedUserName") 146 | .IsUnique() 147 | .HasName("UserNameIndex") 148 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 149 | 150 | b.ToTable("AspNetUsers"); 151 | }); 152 | 153 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 154 | { 155 | b.Property("Id") 156 | .ValueGeneratedOnAdd(); 157 | 158 | b.Property("ClaimType"); 159 | 160 | b.Property("ClaimValue"); 161 | 162 | b.Property("UserId") 163 | .IsRequired(); 164 | 165 | b.HasKey("Id"); 166 | 167 | b.HasIndex("UserId"); 168 | 169 | b.ToTable("AspNetUserClaims"); 170 | }); 171 | 172 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 173 | { 174 | b.Property("LoginProvider"); 175 | 176 | b.Property("ProviderKey"); 177 | 178 | b.Property("ProviderDisplayName"); 179 | 180 | b.Property("UserId") 181 | .IsRequired(); 182 | 183 | b.HasKey("LoginProvider", "ProviderKey"); 184 | 185 | b.HasIndex("UserId"); 186 | 187 | b.ToTable("AspNetUserLogins"); 188 | }); 189 | 190 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 191 | { 192 | b.Property("UserId"); 193 | 194 | b.Property("RoleId"); 195 | 196 | b.HasKey("UserId", "RoleId"); 197 | 198 | b.HasIndex("RoleId"); 199 | 200 | b.ToTable("AspNetUserRoles"); 201 | }); 202 | 203 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 204 | { 205 | b.Property("UserId"); 206 | 207 | b.Property("LoginProvider"); 208 | 209 | b.Property("Name"); 210 | 211 | b.Property("Value"); 212 | 213 | b.HasKey("UserId", "LoginProvider", "Name"); 214 | 215 | b.ToTable("AspNetUserTokens"); 216 | }); 217 | 218 | modelBuilder.Entity("FoodChooser.Models.FoodItem", b => 219 | { 220 | b.HasOne("FoodChooser.Models.FoodList", "FoodList") 221 | .WithMany("Foods") 222 | .HasForeignKey("FoodListId") 223 | .OnDelete(DeleteBehavior.Cascade); 224 | }); 225 | 226 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 227 | { 228 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 229 | .WithMany() 230 | .HasForeignKey("RoleId") 231 | .OnDelete(DeleteBehavior.Cascade); 232 | }); 233 | 234 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 235 | { 236 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 237 | .WithMany() 238 | .HasForeignKey("UserId") 239 | .OnDelete(DeleteBehavior.Cascade); 240 | }); 241 | 242 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 243 | { 244 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 245 | .WithMany() 246 | .HasForeignKey("UserId") 247 | .OnDelete(DeleteBehavior.Cascade); 248 | }); 249 | 250 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 251 | { 252 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 253 | .WithMany() 254 | .HasForeignKey("RoleId") 255 | .OnDelete(DeleteBehavior.Cascade); 256 | 257 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 258 | .WithMany() 259 | .HasForeignKey("UserId") 260 | .OnDelete(DeleteBehavior.Cascade); 261 | }); 262 | 263 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 264 | { 265 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") 266 | .WithMany() 267 | .HasForeignKey("UserId") 268 | .OnDelete(DeleteBehavior.Cascade); 269 | }); 270 | #pragma warning restore 612, 618 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /Server/FoodChooser/Models/FoodItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace FoodChooser.Models 5 | { 6 | public class FoodItem 7 | { 8 | [Key] 9 | public Guid Id { get; set; } 10 | public string ItemName { get; set; } 11 | public FoodList FoodList { get; set; } 12 | public Guid FoodListId { get; set; } 13 | public DateTime Created { get; set; } 14 | public bool IsPublic { get; set; } 15 | public string ImageString { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/FoodChooser/Models/FoodList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FoodChooser.Models 5 | { 6 | public class FoodList 7 | { 8 | public Guid Id { get; set; } 9 | public string Name { get; set; } 10 | public string UserId { get; set; } 11 | public ICollection Foods { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Server/FoodChooser/Models/QueryParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace FoodChooser.Models 7 | { 8 | public class QueryParameters 9 | { 10 | private const int maxPageSize = 50; 11 | public int Page { get; set; } = 1; 12 | 13 | private int _pageSize = maxPageSize; 14 | public int PageSize 15 | { 16 | get { return _pageSize; } 17 | set { _pageSize = (value > maxPageSize) ? maxPageSize : value; } 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/FoodChooser/Models/RegisterBindingModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace FoodChooser.Models 4 | { 5 | public class RegisterBindingModel 6 | { 7 | [Required] 8 | public string Email { get; set; } 9 | 10 | [Required] 11 | public string Username { get; set; } 12 | 13 | [Required] 14 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 15 | [DataType(DataType.Password)] 16 | public string Password { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 20 | public string ConfirmPassword { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Server/FoodChooser/Program.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Repositories; 2 | using FoodChooser.Services.DataBaseInit; 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | 10 | namespace FoodChooser 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | var host = BuildWebHost(args); 17 | 18 | // Initializes db. 19 | using (var scope = host.Services.CreateScope()) 20 | { 21 | var services = scope.ServiceProvider; 22 | try 23 | { 24 | var context = services.GetRequiredService(); 25 | var dbInitializer = services.GetRequiredService(); 26 | dbInitializer.Initialize(context).GetAwaiter().GetResult(); 27 | } 28 | catch (Exception ex) 29 | { 30 | var logger = services.GetRequiredService>(); 31 | logger.LogError(ex, "An error occurred while seeding the database."); 32 | } 33 | } 34 | 35 | host.Run(); 36 | } 37 | 38 | public static IWebHost BuildWebHost(string[] args) => 39 | WebHost.CreateDefaultBuilder(args) 40 | .ConfigureAppConfiguration((context, config) => 41 | { 42 | var env = context.HostingEnvironment; 43 | 44 | config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 45 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 46 | 47 | 48 | }) 49 | .ConfigureLogging((hostingContext, logging) => 50 | { 51 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 52 | logging.AddConsole(); 53 | logging.AddDebug(); 54 | }) 55 | .UseStartup() 56 | .UseDefaultServiceProvider(options => options.ValidateScopes = false) 57 | .Build(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Server/FoodChooser/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:64942/swagger/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "FoodChooser": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:64943/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Server/FoodChooser/Repositories/Food/FoodRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FoodChooser.Models; 4 | using FoodChooser.Helpers; 5 | using System.Collections.Generic; 6 | using System.Linq.Dynamic.Core; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace FoodChooser.Repositories.Food 10 | { 11 | public class FoodRepository : IFoodRepository 12 | { 13 | private readonly FoodChooserDbContext _foodDbContext; 14 | 15 | public FoodRepository(FoodChooserDbContext foodDbContext) 16 | { 17 | _foodDbContext = foodDbContext; 18 | } 19 | 20 | public FoodItem GetSingle(Guid foodItemId, Guid? foodListId = null) 21 | { 22 | if (foodListId.HasValue) 23 | { 24 | return _foodDbContext 25 | .FoodItems 26 | .Include(x => x.FoodList) 27 | .FirstOrDefault(x => x.Id == foodItemId && x.FoodListId == foodListId); 28 | } 29 | 30 | return _foodDbContext 31 | .FoodItems 32 | .Include(x => x.FoodList) 33 | .FirstOrDefault(x => x.Id == foodItemId); 34 | 35 | } 36 | 37 | public void Add(FoodItem item) 38 | { 39 | _foodDbContext.FoodItems.Add(item); 40 | } 41 | 42 | public void Delete(Guid id) 43 | { 44 | FoodItem foodItem = GetSingle(id); 45 | _foodDbContext.FoodItems.Remove(foodItem); 46 | } 47 | 48 | public void Update(FoodItem item) 49 | { 50 | _foodDbContext.FoodItems.Update(item); 51 | } 52 | 53 | public IQueryable GetAll() 54 | { 55 | return _foodDbContext.FoodItems; 56 | } 57 | 58 | public int Count() 59 | { 60 | return _foodDbContext.FoodItems.Count(); 61 | } 62 | 63 | public bool Save() 64 | { 65 | return (_foodDbContext.SaveChanges() >= 0); 66 | } 67 | 68 | public IQueryable GetAllPublic() 69 | { 70 | return _foodDbContext.FoodItems.Include(x => x.FoodList).Where(x => x.IsPublic); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Server/FoodChooser/Repositories/Food/IFoodRepository.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace FoodChooser.Repositories.Food 7 | { 8 | public interface IFoodRepository 9 | { 10 | FoodItem GetSingle(Guid foodItemId, Guid? foodListId); 11 | void Add(FoodItem item); 12 | void Delete(Guid id); 13 | void Update(FoodItem item); 14 | IQueryable GetAll(); 15 | IQueryable GetAllPublic(); 16 | int Count(); 17 | bool Save(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Server/FoodChooser/Repositories/FoodChooserDbContext.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Models; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace FoodChooser.Repositories 7 | { 8 | public class FoodChooserDbContext : IdentityDbContext 9 | { 10 | public FoodChooserDbContext(DbContextOptions options) 11 | : base(options) 12 | { 13 | 14 | } 15 | 16 | public DbSet FoodItems { get; set; } 17 | public DbSet FoodLists { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Server/FoodChooser/Repositories/List/FoodListRepository.cs: -------------------------------------------------------------------------------- 1 |  2 | using FoodChooser.Models; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Linq; 6 | 7 | namespace FoodChooser.Repositories.List 8 | { 9 | public class FoodListRepository : IFoodListRepository 10 | { 11 | private readonly FoodChooserDbContext _foodDbContext; 12 | 13 | public FoodListRepository(FoodChooserDbContext foodDbContext) 14 | { 15 | _foodDbContext = foodDbContext; 16 | } 17 | 18 | public FoodList GetSingle(Guid id, bool withFood) 19 | { 20 | if (withFood) 21 | { 22 | return _foodDbContext.FoodLists.Include(x=>x.Foods).FirstOrDefault(x => x.Id == id); 23 | } 24 | return _foodDbContext.FoodLists.FirstOrDefault(x => x.Id == id); 25 | } 26 | 27 | public void Add(FoodList item) 28 | { 29 | _foodDbContext.FoodLists.Add(item); 30 | } 31 | 32 | public void Delete(Guid id) 33 | { 34 | FoodList foodList = GetSingle(id, true); 35 | _foodDbContext.FoodLists.Remove(foodList); 36 | } 37 | 38 | public void Update(FoodList item) 39 | { 40 | _foodDbContext.FoodLists.Update(item); 41 | } 42 | 43 | public IQueryable GetAll(QueryParameters queryParameters) 44 | { 45 | IQueryable _allItems = _foodDbContext.FoodLists; 46 | 47 | return _allItems 48 | .Skip(queryParameters.PageSize * (queryParameters.Page - 1)) 49 | .Take(queryParameters.PageSize); 50 | } 51 | 52 | public int Count() 53 | { 54 | return _foodDbContext.FoodLists.Count(); 55 | } 56 | 57 | public bool Save() 58 | { 59 | return (_foodDbContext.SaveChanges() >= 0); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Server/FoodChooser/Repositories/List/IFoodListRepository.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Models; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace FoodChooser.Repositories.List 6 | { 7 | public interface IFoodListRepository 8 | { 9 | FoodList GetSingle(Guid id, bool withFood); 10 | void Add(FoodList item); 11 | void Delete(Guid id); 12 | void Update(FoodList item); 13 | IQueryable GetAll(QueryParameters queryParameters); 14 | int Count(); 15 | bool Save(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Server/FoodChooser/Services/DataBaseInit/DatabaseInitializer.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Repositories; 2 | using IdentityModel; 3 | using Microsoft.AspNetCore.Identity; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Claims; 8 | using System.Threading.Tasks; 9 | 10 | namespace FoodChooser.Services.DataBaseInit 11 | { 12 | 13 | public class DatabaseInitializer : IDatabaseInitializer 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly RoleManager _roleManager; 17 | 18 | public DatabaseInitializer( 19 | UserManager userManager, 20 | RoleManager roleManager 21 | ) 22 | { 23 | this._userManager = userManager; 24 | this._roleManager = roleManager; 25 | } 26 | 27 | public async Task Initialize(FoodChooserDbContext context) 28 | { 29 | context.Database.EnsureCreated(); 30 | 31 | if (context.Users.Any()) 32 | { 33 | //context.Users.RemoveRange(context.Users); 34 | //context.SaveChanges(); 35 | return; 36 | } 37 | 38 | // Creates Roles. 39 | await _roleManager.CreateAsync(new IdentityRole("administrator")); 40 | await _roleManager.CreateAsync(new IdentityRole("user")); 41 | 42 | // Seeds an admin user. 43 | var user = new IdentityUser 44 | { 45 | AccessFailedCount = 0, 46 | Email = "admin@gmail.com", 47 | EmailConfirmed = false, 48 | LockoutEnabled = true, 49 | NormalizedEmail = "ADMIN@GMAIL.COM", 50 | NormalizedUserName = "ADMIN@GMAIL.COM", 51 | TwoFactorEnabled = false, 52 | UserName = "admin" 53 | }; 54 | 55 | var result = await _userManager.CreateAsync(user, "adminadmin"); 56 | 57 | if (result.Succeeded) 58 | { 59 | var adminUser = await _userManager.FindByNameAsync(user.UserName); 60 | // Assigns the administrator role. 61 | await _userManager.AddToRoleAsync(adminUser, "administrator"); 62 | // Assigns claims. 63 | var claims = new List { 64 | new Claim(type: JwtClaimTypes.Name, value: user.UserName) 65 | }; 66 | await _userManager.AddClaimsAsync(adminUser, claims); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Server/FoodChooser/Services/DataBaseInit/IDatabaseInitializer.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Repositories; 2 | using System.Threading.Tasks; 3 | 4 | namespace FoodChooser.Services.DataBaseInit 5 | { 6 | public interface IDatabaseInitializer 7 | { 8 | Task Initialize(FoodChooserDbContext context); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Server/FoodChooser/Services/RandomNumber/IRandomNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace FoodChooser.Services 2 | { 3 | public interface IRandomNumberGenerator 4 | { 5 | int GetRandomNumber(int max); 6 | } 7 | } -------------------------------------------------------------------------------- /Server/FoodChooser/Services/RandomNumber/RandomNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace FoodChooser.Services 5 | { 6 | public class RandomNumberGenerator : IRandomNumberGenerator 7 | { 8 | private readonly Random _random; 9 | 10 | public RandomNumberGenerator() 11 | { 12 | _random = new Random((int)DateTime.Now.Ticks & 0x0000FFFF); 13 | } 14 | 15 | public int GetRandomNumber(int max) 16 | { 17 | Thread.Sleep(20); 18 | return _random.Next(0, max); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Server/FoodChooser/Startup.cs: -------------------------------------------------------------------------------- 1 | using FoodChooser.Configuration; 2 | using FoodChooser.Models; 3 | using FoodChooser.Repositories; 4 | using FoodChooser.Repositories.Food; 5 | using FoodChooser.Repositories.List; 6 | using FoodChooser.Services; 7 | using FoodChooser.Services.DataBaseInit; 8 | using FoodChooser.ViewModels; 9 | using IdentityServer4.AccessTokenValidation; 10 | using Microsoft.AspNetCore.Builder; 11 | using Microsoft.AspNetCore.Diagnostics; 12 | using Microsoft.AspNetCore.Hosting; 13 | using Microsoft.AspNetCore.Http; 14 | using Microsoft.AspNetCore.Identity; 15 | using Microsoft.AspNetCore.Mvc; 16 | using Microsoft.AspNetCore.Mvc.Infrastructure; 17 | using Microsoft.AspNetCore.Mvc.Routing; 18 | using Microsoft.EntityFrameworkCore; 19 | using Microsoft.Extensions.Configuration; 20 | using Microsoft.Extensions.DependencyInjection; 21 | using Microsoft.Extensions.Logging; 22 | using Newtonsoft.Json.Serialization; 23 | using Swashbuckle.AspNetCore.Swagger; 24 | 25 | namespace FoodChooser 26 | { 27 | public class Startup 28 | { 29 | public IConfiguration Configuration { get; } 30 | 31 | public Startup(IConfiguration configuration) 32 | { 33 | Configuration = configuration; 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to add services to the container. 37 | // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 38 | public void ConfigureServices(IServiceCollection services) 39 | { 40 | services.AddOptions(); 41 | 42 | services.AddCors(options => 43 | { 44 | options.AddPolicy("AllowAllOrigins", 45 | builder => 46 | { 47 | builder 48 | .AllowAnyOrigin() 49 | .AllowAnyHeader() 50 | .AllowAnyMethod(); 51 | }); 52 | }); 53 | 54 | // Adds framework services. 55 | services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 56 | 57 | services.AddIdentity() 58 | .AddEntityFrameworkStores() 59 | .AddDefaultTokenProviders(); 60 | 61 | services.AddRouting(options => options.LowercaseUrls = true); 62 | services.AddSingleton(); 63 | services.AddScoped(implementationFactory => 64 | { 65 | var actionContext = implementationFactory.GetService().ActionContext; 66 | return new UrlHelper(actionContext); 67 | }); 68 | 69 | // Identity options. 70 | services.Configure(options => 71 | { 72 | // Password settings. 73 | options.Password.RequireDigit = false; 74 | options.Password.RequiredLength = 5; 75 | options.Password.RequireNonAlphanumeric = false; 76 | options.Password.RequireUppercase = false; 77 | options.Password.RequireLowercase = false; 78 | }); 79 | 80 | services.Configure(Configuration.GetSection("AppConfiguration")); 81 | 82 | // Claims-Based Authorization: role claims. 83 | services.AddAuthorization(options => 84 | { 85 | options.AddPolicy("Manage Accounts", policy => policy.RequireRole("administrator")); 86 | options.AddPolicy("Access Resources", policy => policy.RequireRole("administrator", "user")); 87 | options.AddPolicy("Modify Resources", policy => policy.RequireRole("administrator")); 88 | }); 89 | 90 | services.AddIdentityServer() 91 | .AddDeveloperSigningCredential() 92 | .AddInMemoryPersistedGrants() 93 | .AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources()) 94 | .AddInMemoryApiResources(IdentityConfig.GetApiResources()) 95 | .AddInMemoryClients(IdentityConfig.GetClients()) 96 | .AddAspNetIdentity(); 97 | 98 | services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) 99 | .AddIdentityServerAuthentication(options => 100 | { 101 | // options.Authority = "http://localhost:64943/"; 102 | options.Authority = "http://foodchooser.azurewebsites.net/"; 103 | options.RequireHttpsMetadata = false; 104 | options.ApiName = "WebAPI"; 105 | }); 106 | 107 | services.AddScoped(); 108 | services.AddScoped(); 109 | services.AddSingleton(); 110 | services.AddTransient(); 111 | 112 | services.AddMvc().AddJsonOptions(options => 113 | { 114 | options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 115 | }); 116 | 117 | services.AddSwaggerGen(c => 118 | { 119 | c.SwaggerDoc("v1", new Info { Title = "FoodChooserApi", Version = "v1" }); 120 | }); 121 | } 122 | 123 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 124 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 125 | { 126 | loggerFactory.AddConsole(); 127 | 128 | if (env.IsDevelopment()) 129 | { 130 | app.UseDeveloperExceptionPage(); 131 | } 132 | else 133 | { 134 | app.UseExceptionHandler(errorApp => 135 | { 136 | errorApp.Run(async context => 137 | { 138 | context.Response.StatusCode = 500; 139 | context.Response.ContentType = "text/plain"; 140 | var errorFeature = context.Features.Get(); 141 | if (errorFeature != null) 142 | { 143 | var logger = loggerFactory.CreateLogger("Global exception logger"); 144 | logger.LogError(500, errorFeature.Error, errorFeature.Error.Message); 145 | } 146 | 147 | await context.Response.WriteAsync("There was an error"); 148 | }); 149 | }); 150 | } 151 | 152 | app.UseCors("AllowAllOrigins"); 153 | AutoMapper.Mapper.Initialize(mapper => 154 | { 155 | mapper.CreateMap().ReverseMap(); 156 | mapper.CreateMap().ReverseMap(); 157 | }); 158 | 159 | // IdentityServer4.AccessTokenValidation: authentication middleware for the API. 160 | app.UseIdentityServer(); 161 | 162 | app.UseAuthentication(); 163 | 164 | app.UseStaticFiles(); 165 | app.UseDefaultFiles(); 166 | 167 | // Enable middleware to serve generated Swagger as a JSON endpoint. 168 | app.UseSwagger(); 169 | 170 | // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint. 171 | app.UseSwaggerUI(c => 172 | { 173 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "FoodAPICore V1"); 174 | }); 175 | 176 | app.UseMvc(); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Server/FoodChooser/appsettings.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Data Source=localhost\\SQLExpress;Initial Catalog=FoodChooser22;Integrated Security=true" 4 | } 5 | } -------------------------------------------------------------------------------- /Server/FoodChooser/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppConfiguration": { 3 | "ImageSaveFolder": "/FoodImages/", 4 | "DummyImageName": "dummy.png" 5 | } 6 | } -------------------------------------------------------------------------------- /Server/FoodChooser/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"e0d603dd363c2ea9ca1fadea590019a4","Parameters":{"D":"hcOHYYQ7P2I1GidKGEJTDkK77+r5TVd4jOgttrsYOhzS1kWNZZPFez/8tHSHuhGQtxcTbH3bZkZHgAC//0o+1K+rM36gsKeOXMVhGgch/N3bsJMoxIc7E6dNz/t+iZHT2DkrUvpYMeZz2PTHfC9cgtG48rBPWC68fAsqNYa0g+DXfYE+xmdq4AC8fG1/HzxutNBvfNakLQ2SNHJ1UXdvnurtf6DgpK2PgDfJ8424OcIN1fg9R+mNu+ZuB0RoLIYnFAFVGrrAUly+716G6hTc4q0r/zIMpnnCpeykPThR+5ck2ro1bIn/6IoV7OHosznI0znuJLay4ELkvcm8Vv7EJQ==","DP":"d5VDv6Ie0/+52CTY12OxGmJhZw4Tp50aRNBWNGLkUNpzfiCnXUASLKXk6gP6cSK51YvncoDQS/vGd3Mii5dnC9GPMQvifHkjAhSWAGiNdgKwZah84v2ACnIviV/hw52GTa8Tguvh6To6ImcQMe6CViJ2QWbKcZweDjBhtLLZa+0=","DQ":"10y/O1QMuUgTU5nUdqsU2PB9Dd28N76vSNN8Y2a8dAn8fUfA3lwYE0aiIiohNuAkoz3b31wuo7GuXeQxTI9kUf/RKqmpRFfDz60JM/s3XiVp8Z+yNE5aalimpzTuRG14Y9RsHjyGaTbDuLabdexHS5ezh9ddfxiaH01oXHIs2kE=","Exponent":"AQAB","InverseQ":"jU2k3bISPR67+jvaf/jvv7QNeRrVeivluBVrJeEFCXIG/XCM10XzRzZzdMWF7KyfE44dzBzfuBm/iD0X5gzvNqKjhxBvwZ6xVYjcET5NvyF2U30GLc7t6S8Qe4qw2T0Mko+TNcnJ8q2O+D9JqappNC34kDHwndaR8rT5mGH4/Sk=","Modulus":"1cJYrwEycOfwclXIQQvv5hGHh3F8LbkQCeRpyVXq8j4Q743dczhXvBGaUR6E9pN4pcrRxgrsWBNpc6OKRZcBqPYkj86wA4wQLpC+dYrojEY9s2z3ajyI32MBMBRdiklOGMm4k80oe3zfyuuFoiWrgZ8Ohzob7kj5zmwKxBL20kQuo7b23iZVpAp37KQqbU5GreKyL61BIkpRCQWhMfG60TINHqDuMH7+jZE8YdE2sdePgWsoA/znAOy01ovE+BPeois/Vnag99QnMa8Fnn4WLHVqf07x44VOdvSX2+uaU1jd90P+LCHU6k27Uqo90MjakeNvIYH4j3M1e1MkaBWw9Q==","P":"24oSIqo8I53S/f87iZ7Y38TDhx4J0ooh1V0tK3roAMtZ3DO62prd2waCJMCCRFYTmAdvwo71NK5AMRAe+INIJtgMhn9qcEVMp79YK7SoXwGNSnVecR9U3Urs4NRZdxS9HXSDVanksyOqUnpq+UrxEvPEw6sepyM8un/KkUPmAKc=","Q":"+UKGnvvW9hvXmudvXC5cWJbkWiIyv0ogKRyIR6XXmdxUi9A8X6R/Rgk2vevBV9kNLzAvmTd9LXEajZ1fllyMhitIsa+g/GMFiGWzvXOIlJt1wdD8LuPY5OFAr87k6Il244yXBYnKDCSr2JysH0sYIWytOKH1rqArFpwJc3UduQM="}} -------------------------------------------------------------------------------- /Server/FoodChooser/wwwroot/FoodImages/34f6c25a-8045-4c69-b4ab-cff2c8466758.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Server/FoodChooser/wwwroot/FoodImages/34f6c25a-8045-4c69-b4ab-cff2c8466758.png -------------------------------------------------------------------------------- /Server/FoodChooser/wwwroot/FoodImages/7f7c8988-2750-4bf6-9595-805880e10452.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Server/FoodChooser/wwwroot/FoodImages/7f7c8988-2750-4bf6-9595-805880e10452.png -------------------------------------------------------------------------------- /Server/FoodChooser/wwwroot/FoodImages/8267a578-bd32-45ab-ad7e-0e44d7e67786.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Server/FoodChooser/wwwroot/FoodImages/8267a578-bd32-45ab-ad7e-0e44d7e67786.png -------------------------------------------------------------------------------- /Server/FoodChooser/wwwroot/FoodImages/b2ba66c1-8914-42ca-84bf-924aad99da2f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Server/FoodChooser/wwwroot/FoodImages/b2ba66c1-8914-42ca-84bf-924aad99da2f.png -------------------------------------------------------------------------------- /Server/FoodChooser/wwwroot/FoodImages/dummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FabianGosebrink/Foodchooser-ASPNET-Angular-Cross-Platform/0462b0f40bae73a754033676458f7374586d48cb/Server/FoodChooser/wwwroot/FoodImages/dummy.png --------------------------------------------------------------------------------