├── DatingApp ├── DatingApp │ ├── ClientApp │ │ ├── src │ │ │ ├── assets │ │ │ │ ├── .gitkeep │ │ │ │ └── styles-external.css │ │ │ ├── app │ │ │ │ ├── components │ │ │ │ │ ├── orders │ │ │ │ │ │ ├── orders.component.scss │ │ │ │ │ │ ├── orders.component.html │ │ │ │ │ │ └── orders.component.ts │ │ │ │ │ ├── customers │ │ │ │ │ │ ├── customers.component.scss │ │ │ │ │ │ ├── customers.component.html │ │ │ │ │ │ └── customers.component.ts │ │ │ │ │ ├── products │ │ │ │ │ │ ├── products.component.scss │ │ │ │ │ │ ├── products.component.html │ │ │ │ │ │ └── products.component.ts │ │ │ │ │ ├── controls │ │ │ │ │ │ ├── search-box.component.scss │ │ │ │ │ │ ├── notifications-viewer.component.scss │ │ │ │ │ │ ├── roles-management.component.scss │ │ │ │ │ │ ├── statistics-demo.component.scss │ │ │ │ │ │ ├── search-box.component.html │ │ │ │ │ │ ├── banner-demo.component.ts │ │ │ │ │ │ ├── users-management.component.scss │ │ │ │ │ │ ├── user-preferences.component.scss │ │ │ │ │ │ ├── todo-demo.component.scss │ │ │ │ │ │ ├── search-box.component.ts │ │ │ │ │ │ ├── role-editor.component.scss │ │ │ │ │ │ ├── user-info.component.scss │ │ │ │ │ │ ├── banner-demo.component.html │ │ │ │ │ │ ├── notifications-viewer.component.html │ │ │ │ │ │ ├── statistics-demo.component.html │ │ │ │ │ │ ├── roles-management.component.html │ │ │ │ │ │ └── users-management.component.html │ │ │ │ │ ├── home │ │ │ │ │ │ ├── home.component.scss │ │ │ │ │ │ ├── home.component.ts │ │ │ │ │ │ └── home.component.html │ │ │ │ │ ├── settings │ │ │ │ │ │ ├── settings.component.scss │ │ │ │ │ │ ├── settings.component.ts │ │ │ │ │ │ └── settings.component.html │ │ │ │ │ ├── not-found │ │ │ │ │ │ ├── not-found.component.scss │ │ │ │ │ │ ├── not-found.component.ts │ │ │ │ │ │ └── not-found.component.html │ │ │ │ │ ├── about │ │ │ │ │ │ ├── about.component.ts │ │ │ │ │ │ ├── _about.component.scss │ │ │ │ │ │ ├── about.component.scss │ │ │ │ │ │ └── about.component.html │ │ │ │ │ ├── login │ │ │ │ │ │ ├── login.component.scss │ │ │ │ │ │ └── login.component.html │ │ │ │ │ ├── app.component.scss │ │ │ │ │ └── app.component.spec.ts │ │ │ │ ├── assets │ │ │ │ │ ├── styles │ │ │ │ │ │ ├── _custom-vendor.scss │ │ │ │ │ │ ├── styles-vendor.css │ │ │ │ │ │ ├── bootstrap4-compatibility.css │ │ │ │ │ │ ├── vertical-tabs.scss │ │ │ │ │ │ ├── alertify.default.css │ │ │ │ │ │ └── alertify.core.css │ │ │ │ │ ├── images │ │ │ │ │ │ ├── app-icon.ico │ │ │ │ │ │ ├── demo │ │ │ │ │ │ │ ├── banner1.png │ │ │ │ │ │ │ ├── banner2.png │ │ │ │ │ │ │ ├── banner3.png │ │ │ │ │ │ │ └── banner4.png │ │ │ │ │ │ ├── logo-black.png │ │ │ │ │ │ └── logo-white.png │ │ │ │ │ └── themes │ │ │ │ │ │ ├── minty.scss │ │ │ │ │ │ ├── united.scss │ │ │ │ │ │ ├── spacelab.scss │ │ │ │ │ │ ├── cosmo.scss │ │ │ │ │ │ ├── flatly.scss │ │ │ │ │ │ ├── lumen.scss │ │ │ │ │ │ ├── journal.scss │ │ │ │ │ │ ├── cerulean.scss │ │ │ │ │ │ ├── pulse.scss │ │ │ │ │ │ ├── sketchy.scss │ │ │ │ │ │ ├── _app-theme.scss │ │ │ │ │ │ ├── slate.scss │ │ │ │ │ │ ├── superhero.scss │ │ │ │ │ │ └── solar.scss │ │ │ │ ├── models │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── app-theme.model.ts │ │ │ │ │ ├── user-login.model.ts │ │ │ │ │ ├── role.model.ts │ │ │ │ │ ├── user-edit.model.ts │ │ │ │ │ ├── notification.model.ts │ │ │ │ │ ├── login-response.model.ts │ │ │ │ │ ├── user.model.ts │ │ │ │ │ └── permission.model.ts │ │ │ │ ├── directives │ │ │ │ │ ├── autofocus.directive.ts │ │ │ │ │ ├── last-element.directive.ts │ │ │ │ │ ├── equal-validator.directive.ts │ │ │ │ │ ├── bootstrap-tab.directive.ts │ │ │ │ │ ├── bootstrap-toggle.directive.ts │ │ │ │ │ └── bootstrap-datepicker.directive.ts │ │ │ │ ├── services │ │ │ │ │ ├── can-deactivate-guard.service.ts │ │ │ │ │ ├── animations.ts │ │ │ │ │ ├── db-keys.ts │ │ │ │ │ ├── auth-guard.service.ts │ │ │ │ │ ├── app-title.service.ts │ │ │ │ │ ├── jwt-helper.ts │ │ │ │ │ └── app-translation.service.ts │ │ │ │ ├── pipes │ │ │ │ │ └── group-by.pipe.ts │ │ │ │ ├── app-error.handler.ts │ │ │ │ └── app-routing.module.ts │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.spec.json │ │ │ ├── environments │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── tslint.json │ │ │ ├── browserslist │ │ │ ├── main.ts │ │ │ ├── test.ts │ │ │ ├── index.html │ │ │ ├── karma.conf.js │ │ │ └── polyfills.ts │ │ ├── e2e │ │ │ ├── tsconfig.e2e.json │ │ │ ├── src │ │ │ │ ├── app.po.ts │ │ │ │ └── app.e2e-spec.ts │ │ │ └── protractor.conf.js │ │ ├── .editorconfig │ │ ├── tsconfig.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ └── tslint.json │ ├── wwwroot │ │ └── favicon.ico │ ├── Pages │ │ ├── _ViewImports.cshtml │ │ ├── Error.cshtml.cs │ │ └── Error.cshtml │ ├── Helpers │ │ ├── Templates │ │ │ ├── PlainTextTestEmail.template │ │ │ └── TestEmail.template │ │ ├── LoggingEvents.cs │ │ ├── Extensions.cs │ │ ├── MinimumCountAttribute.cs │ │ ├── Utilities.cs │ │ └── EmailTemplates.cs │ ├── ViewModels │ │ ├── ClaimViewModel.cs │ │ ├── OrderViewModel.cs │ │ ├── PermissionViewModel.cs │ │ ├── RoleViewModel.cs │ │ ├── PageHeader.cs │ │ ├── ProductViewModel.cs │ │ ├── CustomerViewModel.cs │ │ ├── AutoMapperProfile.cs │ │ └── UserViewModels.cs │ ├── AppSettings.cs │ ├── Properties │ │ └── launchSettings.json │ ├── web.config │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Authorization │ │ ├── ViewRoleAuthorizationRequirement.cs │ │ ├── ProfileService.cs │ │ ├── Policies.cs │ │ ├── AssignRolesAuthorizationRequirement.cs │ │ └── UserAccountAuthorizationRequirement.cs │ ├── DesignTimeDbContextFactory.cs │ ├── AuthorizeCheckOperationFilter.cs │ ├── tempkey.rsa │ ├── Program.cs │ ├── Controllers │ │ └── CustomerController.cs │ └── IdentityServerConfig.cs └── DAL │ ├── Core │ ├── Enums.cs │ ├── ProfileConstants.cs │ ├── Interfaces │ │ └── IAccountManager.cs │ └── ApplicationPermissions.cs │ ├── Repositories │ ├── Interfaces │ │ ├── IOrdersRepository.cs │ │ ├── IProductRepository.cs │ │ ├── ICustomerRepository.cs │ │ └── IRepository.cs │ ├── OrdersRepository.cs │ ├── ProductRepository.cs │ ├── CustomerRepository.cs │ └── Repository.cs │ ├── Models │ ├── Interfaces │ │ └── IAuditableEntity.cs │ ├── OrderDetail.cs │ ├── AuditableEntity.cs │ ├── ProductCategory.cs │ ├── Customer.cs │ ├── Order.cs │ ├── Product.cs │ ├── ApplicationUser.cs │ └── ApplicationRole.cs │ ├── HttpUnitOfWork.cs │ ├── IUnitOfWork.cs │ ├── DAL.csproj │ └── UnitOfWork.cs ├── DatingApp.sln └── .gitattributes /DatingApp/DatingApp/ClientApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/orders/orders.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/customers/customers.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/products/products.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/_custom-vendor.scss: -------------------------------------------------------------------------------- 1 | $container-max-widths: ( 2 | xl: 1250px 3 | ); 4 | 5 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/search-box.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .search-icon { 3 | pointer-events: none; 4 | } 5 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/notifications-viewer.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .unread { 3 | font-weight: bold; 4 | } 5 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using DatingApp 2 | @namespace DatingApp.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/enums.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export enum Gender { 7 | None, 8 | Female, 9 | Male 10 | } 11 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/app-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/app-icon.ico -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner1.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner2.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner3.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/demo/banner4.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/logo-black.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/DatingApp/HEAD/DatingApp/DatingApp/ClientApp/src/app/assets/images/logo-white.png -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/home/home.component.scss: -------------------------------------------------------------------------------- 1 | button.close { 2 | position: absolute; 3 | top: 0.25rem; 4 | right: 1.5rem; 5 | z-index: 100; 6 | } 7 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/settings/settings.component.scss: -------------------------------------------------------------------------------- 1 | .separator-hr { 2 | margin-top: 0; 3 | margin-bottom: 10px; 4 | } 5 | 6 | [hidden] { 7 | display: none; 8 | } 9 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/Templates/PlainTextTestEmail.template: -------------------------------------------------------------------------------- 1 | Hello, 2 | This email was sent using the plain text test email template. 3 | The test was on {date}. 4 | 5 | Regards, 6 | QuickApp Template -------------------------------------------------------------------------------- /DatingApp/DAL/Core/Enums.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | 8 | namespace DAL.Core 9 | { 10 | public enum Gender 11 | { 12 | None, 13 | Female, 14 | Male 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/Templates/TestEmail.template: -------------------------------------------------------------------------------- 1 |

Hello {user},

2 |

This is a TEST email.

3 |

The request was on {testDate}.

4 |


5 |

Regards,

6 |

QuickApp Template

-------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/not-found/not-found.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .icon-container { 3 | font-size: 5rem; 4 | } 5 | 6 | .error-description { 7 | font-size: 1.5rem; 8 | padding-bottom: 10px; 9 | } 10 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/Interfaces/IOrdersRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | 8 | namespace DAL.Repositories.Interfaces 9 | { 10 | public interface IOrdersRepository : IRepository 11 | { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/Interfaces/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | 8 | namespace DAL.Repositories.Interfaces 9 | { 10 | public interface IProductRepository : IRepository 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [ "node" ] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/minty.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/minty/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/minty/bootswatch'; 6 | 7 | @import "app-theme"; -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/united.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/united/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/united/bootswatch'; 6 | 7 | @import "app-theme"; -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/app-theme.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export interface AppTheme { 7 | id: number; 8 | name: string; 9 | href: string; 10 | isDefault?: boolean; 11 | background: string; 12 | color: string; 13 | isDark?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/spacelab.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/spacelab/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/spacelab/bootswatch'; 6 | 7 | @import "app-theme"; -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/roles-management.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .control-box { 3 | margin-bottom: 5px; 4 | } 5 | 6 | .search-box { 7 | margin: 0; 8 | } 9 | 10 | .nav-item.toolbaritem a { 11 | padding-top: 3px; 12 | padding-bottom: 3px; 13 | min-width: 100px; 14 | } -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/ClaimViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Linq; 8 | 9 | namespace DatingApp.ViewModels 10 | { 11 | public class ClaimViewModel 12 | { 13 | public string Type { get; set; } 14 | public string Value { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { browser, by, element } from 'protractor'; 7 | 8 | export class AppPage { 9 | navigateTo() { 10 | return browser.get('/'); 11 | } 12 | 13 | getAppTitle() { 14 | return element(by.css('app-root .appTitle')).getText(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/Interfaces/IAuditableEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DAL.Models.Interfaces 4 | { 5 | public interface IAuditableEntity 6 | { 7 | string CreatedBy { get; set; } 8 | string UpdatedBy { get; set; } 9 | DateTime CreatedDate { get; set; } 10 | DateTime UpdatedDate { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/OrderViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Linq; 8 | 9 | 10 | namespace DatingApp.ViewModels 11 | { 12 | public class OrderViewModel 13 | { 14 | public int Id { get; set; } 15 | public decimal Discount { get; set; } 16 | public string Comments { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export const environment = { 7 | production: true, 8 | baseUrl: null, // Change this to the address of your backend API if different from frontend address 9 | tokenUrl: null, // For IdentityServer/Authorization Server API. You can set to null if same as baseUrl 10 | loginUrl: '/login' 11 | }; 12 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { fadeInOut } from '../../services/animations'; 3 | 4 | @Component({ 5 | selector: 'about', 6 | templateUrl: './about.component.html', 7 | styleUrls: ['./about.component.scss'], 8 | animations: [fadeInOut] 9 | }) 10 | export class AboutComponent { 11 | } 12 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/products/products.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | -- Sample Page -- 8 |
9 |
10 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/OrdersRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | using Microsoft.EntityFrameworkCore; 8 | using DAL.Repositories.Interfaces; 9 | 10 | namespace DAL.Repositories 11 | { 12 | public class OrdersRepository : Repository, IOrdersRepository 13 | { 14 | public OrdersRepository(DbContext context) : base(context) 15 | { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/cosmo.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/cosmo/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/cosmo/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .form-group .icon-addon #searchInput.form-control { 11 | border-radius: 5px; 12 | } 13 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/orders/orders.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | -- Sample Page -- 8 |
9 |
10 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | using Microsoft.EntityFrameworkCore; 8 | using DAL.Repositories.Interfaces; 9 | 10 | namespace DAL.Repositories 11 | { 12 | public class ProductRepository : Repository, IProductRepository 13 | { 14 | public ProductRepository(DbContext context) : base(context) 15 | { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/flatly.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/flatly/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/flatly/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .app-component { 11 | main { 12 | padding-top: 80px !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/Interfaces/ICustomerRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | using System.Collections.Generic; 8 | 9 | namespace DAL.Repositories.Interfaces 10 | { 11 | public interface ICustomerRepository : IRepository 12 | { 13 | IEnumerable GetTopActiveCustomers(int count); 14 | IEnumerable GetAllCustomersData(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/user-login.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export class UserLogin { 7 | constructor(userName?: string, password?: string, rememberMe?: boolean) { 8 | this.userName = userName; 9 | this.password = password; 10 | this.rememberMe = rememberMe; 11 | } 12 | 13 | userName: string; 14 | password: string; 15 | rememberMe: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/PermissionViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Linq; 8 | 9 | namespace DatingApp.ViewModels 10 | { 11 | public class PermissionViewModel 12 | { 13 | public string Name { get; set; } 14 | public string Value { get; set; } 15 | public string GroupName { get; set; } 16 | public string Description { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/orders/orders.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | import { fadeInOut } from '../../services/animations'; 8 | 9 | @Component({ 10 | selector: 'orders', 11 | templateUrl: './orders.component.html', 12 | styleUrls: ['./orders.component.scss'], 13 | animations: [fadeInOut] 14 | }) 15 | export class OrdersComponent { 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/statistics-demo.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .statistics-container { 3 | font-size: 0.875rem; 4 | } 5 | 6 | .chart-container { 7 | display: block; 8 | } 9 | 10 | 11 | .refresh-btn { 12 | margin-right: 10px; 13 | } 14 | 15 | .chart-type-container { 16 | display: inline-block; 17 | } 18 | 19 | 20 | .chart-type-container .active { 21 | background-color: #e8e8e8; 22 | } 23 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/products/products.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | import { fadeInOut } from '../../services/animations'; 8 | 9 | @Component({ 10 | selector: 'products', 11 | templateUrl: './products.component.html', 12 | styleUrls: ['./products.component.scss'], 13 | animations: [fadeInOut] 14 | }) 15 | export class ProductsComponent { 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | import { fadeInOut } from '../../services/animations'; 8 | 9 | @Component({ 10 | selector: 'not-found', 11 | templateUrl: './not-found.component.html', 12 | styleUrls: ['./not-found.component.scss'], 13 | animations: [fadeInOut] 14 | }) 15 | export class NotFoundComponent { 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { AppPage } from './app.po'; 7 | 8 | describe('DatingApp App', () => { 9 | let page: AppPage; 10 | 11 | beforeEach(() => { 12 | page = new AppPage(); 13 | }); 14 | 15 | it('should display application title: DatingApp', () => { 16 | page.navigateTo(); 17 | expect(page.getAppTitle()).toEqual('DatingApp'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/lumen.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/lumen/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/lumen/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .about-page { 11 | .bg-features { 12 | .logo { 13 | color: #fbfbfb !important; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/customers/customers.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/journal.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/journal/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/journal/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .about-page { 11 | .bg-features { 12 | .logo { 13 | color: $primary !important; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/autofocus.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, ElementRef, OnInit } from '@angular/core'; 7 | 8 | 9 | @Directive({ 10 | selector: '[autofocus]' 11 | }) 12 | export class AutofocusDirective implements OnInit { 13 | constructor(public elementRef: ElementRef) { } 14 | 15 | ngOnInit() { 16 | setTimeout(() => this.elementRef.nativeElement['focus'](), 500); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/customers/customers.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | import { fadeInOut } from '../../services/animations'; 8 | 9 | 10 | @Component({ 11 | selector: 'customers', 12 | templateUrl: './customers.component.html', 13 | styleUrls: ['./customers.component.scss'], 14 | animations: [fadeInOut] 15 | }) 16 | export class CustomersComponent { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 12 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/about/_about.component.scss: -------------------------------------------------------------------------------- 1 | .pageHeader { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .reduced-font { 6 | line-height: 1.4; 7 | } 8 | 9 | .bg-features { 10 | border-radius: $btn-border-radius; 11 | } 12 | 13 | .bg-features ul { 14 | position: relative; 15 | z-index: 100; 16 | } 17 | 18 | .bg-features .logo { 19 | font-size: 200px; 20 | position: absolute; 21 | top: 2rem; 22 | right: 10px; 23 | } 24 | -------------------------------------------------------------------------------- /DatingApp/DAL/HttpUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Core; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace DAL 10 | { 11 | public class HttpUnitOfWork : UnitOfWork 12 | { 13 | public HttpUnitOfWork(ApplicationDbContext context, IHttpContextAccessor httpAccessor) : base(context) 14 | { 15 | context.CurrentUserId = httpAccessor.HttpContext?.User.FindFirst(ClaimConstants.Subject)?.Value?.Trim(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/cerulean.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/cerulean/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/cerulean/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | 11 | .app-component { 12 | .navbar { 13 | .popover-header { 14 | color: $headings-color !important; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DatingApp/DAL/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Repositories.Interfaces; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DAL 14 | { 15 | public interface IUnitOfWork 16 | { 17 | ICustomerRepository Customers { get; } 18 | IProductRepository Products { get; } 19 | IOrdersRepository Orders { get; } 20 | 21 | 22 | int SaveChanges(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/pulse.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/pulse/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/pulse/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .form-group .icon-addon #searchInput.form-control { 11 | border-radius: 0; 12 | } 13 | 14 | 15 | .app-component { 16 | main { 17 | padding-top: 90px !important; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/OrderDetail.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace DAL.Models 3 | { 4 | public class OrderDetail : AuditableEntity 5 | { 6 | public int Id { get; set; } 7 | public decimal UnitPrice { get; set; } 8 | public int Quantity { get; set; } 9 | public decimal Discount { get; set; } 10 | public int ProductId { get; set; } 11 | public Product Product { get; set; } 12 | public int OrderId { get; set; } 13 | public Order Order { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/AuditableEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using DAL.Models.Interfaces; 4 | 5 | namespace DAL.Models 6 | { 7 | public class AuditableEntity : IAuditableEntity 8 | { 9 | [MaxLength(256)] 10 | public string CreatedBy { get; set; } 11 | [MaxLength(256)] 12 | public string UpdatedBy { get; set; } 13 | public DateTime UpdatedDate { get; set; } 14 | public DateTime CreatedDate { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/about/about.component.scss: -------------------------------------------------------------------------------- 1 | .pageHeader { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .reduced-font { 6 | line-height: 1.4; 7 | } 8 | 9 | .bg-features { 10 | border-radius: 0.25rem; 11 | padding: 0.5rem; 12 | margin-bottom: 0.5rem; 13 | } 14 | 15 | .bg-features ul { 16 | position: relative; 17 | z-index: 100; 18 | } 19 | 20 | .bg-features .logo { 21 | font-size: 200px; 22 | position: absolute; 23 | top: 2rem; 24 | right: 10px; 25 | } 26 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/styles-vendor.css: -------------------------------------------------------------------------------- 1 | @import '~ngx-toasta/styles/style-bootstrap.css'; 2 | @import '~@swimlane/ngx-datatable/release/assets/icons.css'; 3 | @import '~bootstrap-toggle/css/bootstrap-toggle.css'; 4 | @import '~bootstrap-select/dist/css/bootstrap-select.css'; 5 | @import '~bootstrap-datepicker/dist/css/bootstrap-datepicker3.css'; 6 | @import '~font-awesome/css/font-awesome.css'; 7 | @import 'alertify.core.css'; 8 | @import 'alertify.bootstrap.css'; 9 | @import 'bootstrap4-compatibility.css'; 10 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2018", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/search-box.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/role.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Permission } from './permission.model'; 7 | 8 | 9 | export class Role { 10 | 11 | constructor(name?: string, description?: string, permissions?: Permission[]) { 12 | 13 | this.name = name; 14 | this.description = description; 15 | this.permissions = permissions; 16 | } 17 | 18 | public id: string; 19 | public name: string; 20 | public description: string; 21 | public usersCount: number; 22 | public permissions: Permission[]; 23 | } 24 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | import { fadeInOut } from '../../services/animations'; 8 | import { ConfigurationService } from '../../services/configuration.service'; 9 | 10 | 11 | @Component({ 12 | selector: 'home', 13 | templateUrl: './home.component.html', 14 | styleUrls: ['./home.component.scss'], 15 | animations: [fadeInOut] 16 | }) 17 | export class HomeComponent { 18 | constructor(public configurations: ConfigurationService) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/banner-demo.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component } from '@angular/core'; 7 | 8 | 9 | 10 | @Component({ 11 | selector: 'banner-demo', 12 | templateUrl: './banner-demo.component.html' 13 | }) 14 | export class BannerDemoComponent { 15 | banner1 = require('../../assets/images/demo/banner1.png'); 16 | banner2 = require('../../assets/images/demo/banner2.png'); 17 | banner3 = require('../../assets/images/demo/banner3.png'); 18 | banner4 = require('../../assets/images/demo/banner4.png'); 19 | } 20 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/last-element.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, Input, Output, EventEmitter } from '@angular/core'; 7 | 8 | 9 | @Directive({ 10 | selector: '[lastElement]' 11 | }) 12 | export class LastElementDirective { 13 | @Input() 14 | set lastElement(isLastElement: boolean) { 15 | 16 | if (isLastElement) { 17 | setTimeout(() => { 18 | this.lastFunction.emit(); 19 | }); 20 | } 21 | } 22 | 23 | @Output() 24 | lastFunction = new EventEmitter(); 25 | } 26 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/LoggingEvents.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using Microsoft.Extensions.Logging; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DatingApp.Helpers 14 | { 15 | public static class LoggingEvents 16 | { 17 | public static readonly EventId INIT_DATABASE = new EventId(101, "Error whilst creating and seeding database"); 18 | public static readonly EventId SEND_EMAIL = new EventId(201, "Error whilst sending email"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/user-edit.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { User } from './user.model'; 7 | 8 | 9 | export class UserEdit extends User { 10 | constructor(currentPassword?: string, newPassword?: string, confirmPassword?: string) { 11 | super(); 12 | 13 | this.currentPassword = currentPassword; 14 | this.newPassword = newPassword; 15 | this.confirmPassword = confirmPassword; 16 | } 17 | 18 | public currentPassword: string; 19 | public newPassword: string; 20 | public confirmPassword: string; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/ProductCategory.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace DAL.Models 10 | { 11 | public class ProductCategory : AuditableEntity 12 | { 13 | public int Id { get; set; } 14 | public string Name { get; set; } 15 | public string Description { get; set; } 16 | public string Icon { get; set; } 17 | public DateTime DateCreated { get; set; } 18 | public DateTime DateModified { get; set; } 19 | 20 | 21 | public ICollection Products { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DatingApp/DAL/DAL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 2.7.2 6 | Data Access Layer for the Quick Application template 7 | Copyright © 2019 www.ebenmonney.com 8 | https://www.ebenmonney.com/quickapp 9 | EBENMONNEY 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/can-deactivate-guard.service.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { CanDeactivate } from '@angular/router'; 8 | import { Observable } from 'rxjs'; 9 | 10 | export interface CanComponentDeactivate { 11 | canDeactivate: () => Observable | Promise | boolean; 12 | } 13 | 14 | @Injectable() 15 | export class CanDeactivateGuard implements CanDeactivate { 16 | canDeactivate(component: CanComponentDeactivate) { 17 | return component.canDeactivate ? component.canDeactivate() : true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/RoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DatingApp.ViewModels 4 | { 5 | public class RoleViewModel 6 | { 7 | public string Id { get; set; } 8 | 9 | [Required(ErrorMessage = "Role name is required"), StringLength(200, MinimumLength = 2, ErrorMessage = "Role name must be between 2 and 200 characters")] 10 | public string Name { get; set; } 11 | 12 | public string Description { get; set; } 13 | 14 | public int UsersCount { get; set; } 15 | 16 | public PermissionViewModel[] Permissions { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/notification.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Utilities } from '../services/utilities'; 7 | 8 | 9 | export class Notification { 10 | 11 | 12 | public id: number; 13 | public header: string; 14 | public body: string; 15 | public isRead: boolean; 16 | public isPinned: boolean; 17 | public date: Date; 18 | 19 | public static Create(data: {}) { 20 | const n = new Notification(); 21 | Object.assign(n, data); 22 | 23 | if (n.date) { 24 | n.date = Utilities.parseDate(n.date); 25 | } 26 | 27 | return n; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/users-management.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .user-role { 3 | font-size: 0.8em !important; 4 | margin-right: 1px; 5 | } 6 | 7 | .control-box { 8 | margin-bottom: 5px; 9 | } 10 | 11 | .search-box { 12 | margin: 0; 13 | } 14 | 15 | .nav-item.toolbaritem a { 16 | padding-top: 3px; 17 | padding-bottom: 3px; 18 | min-width: 100px; 19 | } 20 | 21 | .user-disabled { 22 | color: #777; 23 | font-style: italic; 24 | } 25 | 26 | .locked-out { 27 | background-color: orangered; 28 | color: whitesmoke; 29 | width: 100%; 30 | display: inline-block; 31 | padding-left: 5px; 32 | } -------------------------------------------------------------------------------- /DatingApp/DAL/Models/Customer.cs: -------------------------------------------------------------------------------- 1 | using DAL.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DAL.Models 6 | { 7 | public class Customer : AuditableEntity 8 | { 9 | public int Id { get; set; } 10 | public string Name { get; set; } 11 | public string Email { get; set; } 12 | public string PhoneNumber { get; set; } 13 | public string Address { get; set; } 14 | public string City { get; set; } 15 | public Gender Gender { get; set; } 16 | public DateTime DateCreated { get; set; } 17 | public DateTime DateModified { get; set; } 18 | public ICollection Orders { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |
{{'notFound.404' | translate}}
8 |
{{'notFound.pageNotFound' | translate}}
9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { enableProdMode } from '@angular/core'; 7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 8 | 9 | import { AppModule } from './app/app.module'; 10 | import { environment } from './environments/environment'; 11 | 12 | export function getBaseUrl() { 13 | return document.getElementsByTagName('base')[0].href; 14 | } 15 | 16 | const providers = [ 17 | { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } 18 | ]; 19 | 20 | 21 | if (environment.production) { 22 | enableProdMode(); 23 | } 24 | 25 | platformBrowserDynamic(providers).bootstrapModule(AppModule) 26 | .catch(err => console.error(err)); 27 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DAL.Models 5 | { 6 | public class Order : AuditableEntity 7 | { 8 | public int Id { get; set; } 9 | public decimal Discount { get; set; } 10 | public string Comments { get; set; } 11 | public DateTime DateCreated { get; set; } 12 | public DateTime DateModified { get; set; } 13 | public string CashierId { get; set; } 14 | public ApplicationUser Cashier { get; set; } 15 | public int CustomerId { get; set; } 16 | public Customer Customer { get; set; } 17 | public ICollection OrderDetails { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/sketchy.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/sketchy/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/sketchy/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | input[type="radio"], input[type="checkbox"] { 11 | width: 0; 12 | height: 0; 13 | margin-right: -40px; 14 | margin-left: 0; 15 | } 16 | 17 | .card { 18 | overflow: visible; 19 | } 20 | 21 | .bg-light { 22 | background-color: #f8f9fa !important; 23 | } 24 | 25 | 26 | .about-page { 27 | .bg-features { 28 | .logo { 29 | color: #f1f1f1 !important; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/bootstrap4-compatibility.css: -------------------------------------------------------------------------------- 1 | /* 2 | Make bootstrap-select corrections 3 | */ 4 | .bootstrap-select .dropdown-menu li a span.text { 5 | width: 95% !important; /*To prevent horizontal scrollbars in some scenarios. Eg when ticks are shown*/ 6 | } 7 | 8 | 9 | 10 | 11 | /* Bootstrap Toggle v2.2.2 corrections for Bootsrtap 4*/ 12 | .toggle-off { 13 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 14 | } 15 | 16 | .toggle.off { 17 | border-color: rgba(0, 0, 0, .25); 18 | } 19 | 20 | .toggle-handle { 21 | background-color: white; 22 | border: thin rgba(0, 0, 0, .25) solid; 23 | } 24 | 25 | .toggle-group .toggle-on, .toggle-group .toggle-off { 26 | padding-top: 0.2rem; 27 | } 28 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/AppSettings.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace DatingApp 13 | { 14 | public class AppSettings 15 | { 16 | public SmtpConfig SmtpConfig { get; set; } 17 | 18 | } 19 | 20 | 21 | 22 | public class SmtpConfig 23 | { 24 | public string Host { get; set; } 25 | public int Port { get; set; } 26 | public bool UseSSL { get; set; } 27 | 28 | public string Name { get; set; } 29 | public string Username { get; set; } 30 | public string EmailAddress { get; set; } 31 | public string Password { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.AspNetCore.Mvc.RazorPages; 13 | 14 | namespace DatingApp.Pages 15 | { 16 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 17 | public class ErrorModel : PageModel 18 | { 19 | public string RequestId { get; set; } 20 | 21 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 22 | 23 | public void OnGet() 24 | { 25 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/test.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 7 | 8 | import 'zone.js/dist/zone-testing'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | declare const require: any; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5050/", 7 | "sslPort": 44350 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "DatingApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:44350;http://localhost:5050", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /DatingApp/DatingApp/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/pipes/group-by.pipe.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Pipe, PipeTransform } from '@angular/core'; 7 | 8 | 9 | 10 | @Pipe({ name: 'groupBy' }) 11 | export class GroupByPipe implements PipeTransform { 12 | 13 | transform(value: Array, field: string): Array { 14 | 15 | if (!value) { 16 | return value; 17 | } 18 | 19 | const groupedObj = value.reduce((prev, cur) => { 20 | if (!prev[cur[field]]) { 21 | prev[cur[field]] = [cur]; 22 | } else { 23 | prev[cur[field]].push(cur); 24 | } 25 | 26 | return prev; 27 | }, {}); 28 | 29 | return Object.keys(groupedObj).map(key => ({ key, value: groupedObj[key] })); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/PageHeader.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace DatingApp.ViewModels 13 | { 14 | public class PageHeader 15 | { 16 | public PageHeader(int currentPage, int itemsPerPage, int totalItems, int totalPages) 17 | { 18 | this.CurrentPage = currentPage; 19 | this.ItemsPerPage = itemsPerPage; 20 | this.TotalItems = totalItems; 21 | this.TotalPages = totalPages; 22 | } 23 | 24 | 25 | public int CurrentPage { get; set; } 26 | public int ItemsPerPage { get; set; } 27 | public int TotalItems { get; set; } 28 | public int TotalPages { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/user-preferences.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .separator-hr { 3 | margin: 7px 5px; 4 | border-top-style: dashed; 5 | } 6 | 7 | .subseparator-hr { 8 | margin: 7px 5px; 9 | border-top-style: none; 10 | } 11 | 12 | .last-separator-hr { 13 | margin-top: 7px; 14 | } 15 | 16 | .form-group { 17 | margin-top: 0; 18 | margin-bottom: 0; 19 | } 20 | 21 | .form-control-plaintext { 22 | min-height: 0; 23 | } 24 | 25 | .checkbox { 26 | padding-top: 0; 27 | } 28 | 29 | .col-reset-default { 30 | padding-right: 0; 31 | } 32 | 33 | .col-set-default { 34 | padding-left: 5px; 35 | } 36 | 37 | .col-reset-default .btn, 38 | .col-set-default .btn { 39 | min-width: 150px; 40 | } 41 | 42 | @media (min-width: 768px) { 43 | .col-form-label { 44 | padding-top: 5px; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/ProductViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Linq; 8 | 9 | 10 | namespace DatingApp.ViewModels 11 | { 12 | public class ProductViewModel 13 | { 14 | public int Id { get; set; } 15 | public string Name { get; set; } 16 | public string Description { get; set; } 17 | public string Icon { get; set; } 18 | public decimal BuyingPrice { get; set; } 19 | public decimal SellingPrice { get; set; } 20 | public int UnitsInStock { get; set; } 21 | public bool IsActive { get; set; } 22 | public bool IsDiscontinued { get; set; } 23 | public string ProductCategoryName { get; set; } 24 | public DateTime DateCreated { get; set; } 25 | public DateTime DateModified { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/_app-theme.scss: -------------------------------------------------------------------------------- 1 | 2 | .ngx-datatable.material { 3 | &.colored-header { 4 | .datatable-header { 5 | padding: .5rem 0; 6 | color: theme-color("light"); 7 | background-color: theme-color("primary"); 8 | } 9 | } 10 | } 11 | 12 | .nav-tabs { 13 | &--left { 14 | .nav-link.active { 15 | border-right-color: $body-bg !important; 16 | 17 | &:hover { 18 | border-right-color: $body-bg !important; 19 | } 20 | } 21 | } 22 | } 23 | 24 | 25 | .about-page { 26 | .bg-features { 27 | @if $enable-rounded { 28 | border-radius: $border-radius !important; 29 | } 30 | @else { 31 | border-radius: 0 !important; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/slate.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/slate/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/slate/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .bg-light { 11 | background-color: $primary !important; 12 | } 13 | 14 | 15 | .ngx-datatable.material.table-striped { 16 | .datatable-row-even { 17 | background: $gray-700; 18 | } 19 | } 20 | 21 | 22 | .app-component { 23 | .navbar { 24 | 25 | a.user-name { 26 | border: none; 27 | } 28 | 29 | .popover-header { 30 | color: $body-color !important; 31 | } 32 | } 33 | 34 | footer { 35 | background-color: $primary !important; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/login-response.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { PermissionValues } from './permission.model'; 7 | 8 | 9 | export interface LoginResponse { 10 | access_token: string; 11 | refresh_token: string; 12 | expires_in: number; 13 | token_type: string; 14 | } 15 | 16 | 17 | export interface AccessToken { 18 | nbf: number; 19 | exp: number; 20 | iss: string; 21 | aud: string | string[]; 22 | client_id: string; 23 | sub: string; 24 | auth_time: number; 25 | idp: string; 26 | role: string | string[]; 27 | permission: PermissionValues | PermissionValues[]; 28 | name: string; 29 | email: string; 30 | phone_number: string; 31 | fullname: string; 32 | jobtitle: string; 33 | configuration: string; 34 | scope: string | string[]; 35 | amr: string[]; 36 | } 37 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/animations.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { animate, state, style, transition, trigger } from '@angular/animations'; 7 | 8 | 9 | 10 | export const fadeInOut = trigger('fadeInOut', [ 11 | transition(':enter', [style({ opacity: 0 }), animate('0.4s ease-in', style({ opacity: 1 }))]), 12 | transition(':leave', [animate('0.4s 10ms ease-out', style({ opacity: 0 }))]) 13 | ]); 14 | 15 | 16 | 17 | export function flyInOut(duration: number = 0.2) { 18 | return trigger('flyInOut', [ 19 | state('in', style({ opacity: 1, transform: 'translateX(0)' })), 20 | transition('void => *', [style({ opacity: 0, transform: 'translateX(-100%)' }), animate(`${duration}s ease-in`)]), 21 | transition('* => void', [animate(`${duration}s 10ms ease-out`, style({ opacity: 0, transform: 'translateX(100%)' }))]) 22 | ]); 23 | } 24 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | # Only exists if Bazel was run 9 | /bazel-out 10 | 11 | # dependencies 12 | /node_modules 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | /.vs 26 | 27 | # IDE - VSCode 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | .history/* 34 | 35 | # misc 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/todo-demo.component.scss: -------------------------------------------------------------------------------- 1 | 2 | input.form-control { 3 | border-left-width: 5px; 4 | } 5 | 6 | .control-box { 7 | margin-bottom: 5px; 8 | } 9 | 10 | .search-box { 11 | margin: 0; 12 | } 13 | 14 | .nav-item.toolbaritem a { 15 | padding-top: 3px; 16 | padding-bottom: 3px; 17 | min-width: 100px; 18 | font-weight: bold; 19 | } 20 | 21 | 22 | .completed { 23 | text-decoration: line-through; 24 | } 25 | 26 | .form-check { 27 | margin: 0; 28 | } 29 | 30 | 31 | .inline-label { 32 | width: 100%; 33 | min-height: 1rem; 34 | display: inline-block; 35 | } 36 | 37 | .inline-editor { 38 | width: 100%; 39 | } 40 | 41 | .description-form-group { 42 | margin-bottom: 5px; 43 | } 44 | 45 | .actionBtn-form-group { 46 | margin: 0; 47 | } 48 | 49 | .edit-last-separator-hr { 50 | margin: 10px 0; 51 | } -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/search-box.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core'; 7 | 8 | @Component({ 9 | selector: 'search-box', 10 | templateUrl: './search-box.component.html', 11 | styleUrls: ['./search-box.component.scss'] 12 | }) 13 | export class SearchBoxComponent { 14 | 15 | @Input() 16 | placeholder = 'Search...'; 17 | 18 | @Output() 19 | searchChange = new EventEmitter(); 20 | 21 | @ViewChild('searchInput') 22 | searchInput: ElementRef; 23 | 24 | 25 | onValueChange(value: string) { 26 | setTimeout(() => this.searchChange.emit(value)); 27 | } 28 | 29 | 30 | clear() { 31 | this.searchInput.nativeElement.value = ''; 32 | this.onValueChange(this.searchInput.nativeElement.value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=DatingApp;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | 6 | "ApplicationUrl": "http://localhost:5050", 7 | 8 | "SmtpConfig": { 9 | "Host": "mail.ebenmonney.com", 10 | "Port": 25, 11 | "UseSSL": false, 12 | "Name": "DatingApp Template", 13 | "Username": "quickapp@ebenmonney.com", 14 | "EmailAddress": "quickapp@ebenmonney.com", 15 | "Password": "tempP@ss123" 16 | }, 17 | 18 | // LogLevel Severity: "Trace", "Debug", "Information", "Warning", "Error", "Critical", "None" 19 | "Logging": { 20 | "PathFormat": "Logs/log-{Date}.log", 21 | "LogLevel": { 22 | "Default": "Debug", 23 | "System": "Error", // "Information", 24 | "Microsoft": "Error" // "Information", 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // Protractor configuration file, see link for more information 7 | // https://github.com/angular/protractor/blob/master/lib/config.ts 8 | 9 | const { SpecReporter } = require('jasmine-spec-reporter'); 10 | 11 | exports.config = { 12 | allScriptsTimeout: 11000, 13 | specs: [ 14 | './src/**/*.e2e-spec.ts' 15 | ], 16 | capabilities: { 17 | 'browserName': 'chrome' 18 | }, 19 | directConnect: true, 20 | baseUrl: 'http://localhost:4200/', 21 | framework: 'jasmine', 22 | jasmineNodeOpts: { 23 | showColors: true, 24 | defaultTimeoutInterval: 30000, 25 | print: function () { } 26 | }, 27 | onPrepare() { 28 | require('ts-node').register({ 29 | project: require('path').join(__dirname, './tsconfig.e2e.json') 30 | }); 31 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/superhero.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/superhero/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/superhero/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | 11 | .ngx-datatable.material { 12 | &.table-striped { 13 | .datatable-row-even { 14 | background: $secondary; 15 | } 16 | } 17 | 18 | &.table-hover:not(.cell-selection) { 19 | .datatable-body-row:hover, 20 | .datatable-body-row:hover .datatable-row-group { 21 | background-color: $gray-600 !important; 22 | } 23 | } 24 | 25 | &.colored-header { 26 | .datatable-header { 27 | color: $body-bg; 28 | } 29 | } 30 | } 31 | 32 | 33 | .app-component { 34 | footer { 35 | background-color: $gray-700 !important; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/login/login.component.scss: -------------------------------------------------------------------------------- 1 | .boxshadow { 2 | position: relative; 3 | box-shadow: 1px 2px 4px rgba(0, 0, 0, .5); 4 | padding: 10px; 5 | background: white; 6 | } 7 | 8 | 9 | .boxshadow::after { 10 | content: ''; 11 | position: absolute; 12 | z-index: -1; /* hide shadow behind image */ 13 | box-shadow: 0 15px 20px rgba(0, 0, 0, 0.3); 14 | width: 70%; 15 | left: 15%; /* one half of the remaining 30% */ 16 | height: 100px; 17 | bottom: 0; 18 | } 19 | 20 | 21 | .cardshadow { 22 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 23 | } 24 | 25 | 26 | .last-control-group { 27 | margin-bottom: -0.75rem; 28 | } 29 | 30 | 31 | .h-90 { 32 | height: 90%; 33 | } 34 | 35 | 36 | @media (min-width: 768px) { 37 | .login-container { 38 | width: 700px; 39 | } 40 | } 41 | 42 | 43 | @media (min-width: 1200px) { 44 | .login-container { 45 | width: 730px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/Interfaces/IRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Linq.Expressions; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DAL.Repositories.Interfaces 14 | { 15 | public interface IRepository where TEntity : class 16 | { 17 | void Add(TEntity entity); 18 | void AddRange(IEnumerable entities); 19 | 20 | void Update(TEntity entity); 21 | void UpdateRange(IEnumerable entities); 22 | 23 | void Remove(TEntity entity); 24 | void RemoveRange(IEnumerable entities); 25 | 26 | int Count(); 27 | 28 | IEnumerable Find(Expression> predicate); 29 | TEntity GetSingleOrDefault(Expression> predicate); 30 | TEntity Get(int id); 31 | IEnumerable GetAll(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=SQLEXPRESS01;Database=DatingApp;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | 6 | //Note: In Production change this to your actual host (e.g. https://quickapp.ebenmonney.com) 7 | "ApplicationUrl": "http://localhost:5050", 8 | "HttpsRedirectionPort": 443, //Set this to enable https redirection 9 | 10 | "SmtpConfig": { 11 | "Host": "mail.ebenmonney.com", 12 | "Port": 25, 13 | "UseSSL": false, 14 | "Name": "DatingApp Template", 15 | "Username": "quickapp@ebenmonney.com", 16 | "EmailAddress": "quickapp@ebenmonney.com", 17 | "Password": "tempP@ss123" 18 | }, 19 | 20 | // LogLevel Severity: "Trace", "Debug", "Information", "Warning", "Error", "Critical", "None" 21 | "Logging": { 22 | "PathFormat": "Logs/log-{Date}.log", 23 | "LogLevel": { 24 | "Default": "Warning" 25 | } 26 | }, 27 | "AllowedHosts": "*" 28 | } 29 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // This file can be replaced during build by using the `fileReplacements` array. 7 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 8 | // The list of file replacements can be found in `angular.json`. 9 | 10 | export const environment = { 11 | production: false, 12 | baseUrl: null, // Change this to the address of your backend API if different from frontend address 13 | tokenUrl: null, // For IdentityServer/Authorization Server API. You can set to null if same as baseUrl 14 | loginUrl: '/login' 15 | }; 16 | 17 | /* 18 | * For easier debugging in development mode, you can import the following file 19 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 20 | * 21 | * This import should be commented out in production mode because it will have a negative impact 22 | * on performance if an error is thrown. 23 | */ 24 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 25 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/db-keys.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable } from '@angular/core'; 7 | 8 | @Injectable() 9 | export class DBkeys { 10 | 11 | public static readonly CURRENT_USER = 'current_user'; 12 | public static readonly USER_PERMISSIONS = 'user_permissions'; 13 | public static readonly ACCESS_TOKEN = 'access_token'; 14 | public static readonly REFRESH_TOKEN = 'refresh_token'; 15 | public static readonly TOKEN_EXPIRES_IN = 'expires_in'; 16 | 17 | public static readonly REMEMBER_ME = 'remember_me'; 18 | 19 | 20 | public static readonly LANGUAGE = 'language'; 21 | public static readonly HOME_URL = 'home_url'; 22 | public static readonly THEME_ID = 'themeId'; 23 | public static readonly SHOW_DASHBOARD_STATISTICS = 'show_dashboard_statistics'; 24 | public static readonly SHOW_DASHBOARD_NOTIFICATIONS = 'show_dashboard_notifications'; 25 | public static readonly SHOW_DASHBOARD_TODO = 'show_dashboard_todo'; 26 | public static readonly SHOW_DASHBOARD_BANNER = 'show_dashboard_banner'; 27 | } 28 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/role-editor.component.scss: -------------------------------------------------------------------------------- 1 | .row:not(:last-child) { 2 | /*border-bottom: 1px solid #ccc;*/ 3 | } 4 | 5 | .separator-hr { 6 | margin: 0 5px; 7 | border-top-style: dashed; 8 | } 9 | 10 | .edit-separator-hr { 11 | margin: 10px 5px; 12 | border-top-style: dashed; 13 | } 14 | 15 | .last-separator-hr { 16 | margin-top: 5px; 17 | } 18 | 19 | .edit-last-separator-hr { 20 | margin-top: 15px; 21 | } 22 | 23 | 24 | .form-group { 25 | margin-top: 0; 26 | margin-bottom: 0; 27 | } 28 | 29 | input.form-control { 30 | border-left-width: 5px; 31 | } 32 | 33 | .invalid-feedback { 34 | display: block; 35 | } 36 | 37 | .group-name { 38 | padding-top: 0; 39 | font-weight: 500; 40 | padding-right: 0; 41 | } 42 | 43 | .permissionsColumn { 44 | margin-bottom: 20px; 45 | } 46 | 47 | .permissionsRow { 48 | margin: 0 15px; 49 | } 50 | 51 | 52 | .well-sm { 53 | padding: 0.5rem; 54 | } 55 | 56 | @media (min-width: 992px) { 57 | .user-enabled { 58 | margin-left: 40px; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Authorization/ViewRoleAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Core; 7 | using Microsoft.AspNetCore.Authorization; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace DatingApp.Authorization 13 | { 14 | public class ViewRoleAuthorizationRequirement : IAuthorizationRequirement 15 | { 16 | 17 | } 18 | 19 | 20 | 21 | public class ViewRoleAuthorizationHandler : AuthorizationHandler 22 | { 23 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewRoleAuthorizationRequirement requirement, string roleName) 24 | { 25 | if (context.User == null) 26 | return Task.CompletedTask; 27 | 28 | if (context.User.HasClaim(ClaimConstants.Permission, ApplicationPermissions.ViewRoles) || context.User.IsInRole(roleName)) 29 | context.Succeed(requirement); 30 | 31 | return Task.CompletedTask; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/user-info.component.scss: -------------------------------------------------------------------------------- 1 | .row:not(:last-child) { 2 | /*border-bottom: 1px solid #ccc;*/ 3 | } 4 | 5 | .separator-hr { 6 | margin: 0 5px; 7 | border-top-style: dashed; 8 | } 9 | 10 | .edit-separator-hr { 11 | margin: 10px 5px; 12 | border-top-style: dashed; 13 | } 14 | 15 | .last-separator-hr { 16 | margin-top: 5px; 17 | } 18 | 19 | .edit-last-separator-hr { 20 | margin-top: 15px; 21 | } 22 | 23 | .password-separator-hr { 24 | margin: 5px; 25 | border-style: none; 26 | } 27 | 28 | 29 | .form-group { 30 | margin-top: 0; 31 | margin-bottom: 0; 32 | } 33 | 34 | input.form-control { 35 | border-left-width: 5px; 36 | } 37 | 38 | .invalid-feedback { 39 | display: block; 40 | } 41 | 42 | .password-well { 43 | padding: 0.5rem; 44 | } 45 | 46 | .hint-sm { 47 | display: block; 48 | } 49 | 50 | .form-check.user-enabled { 51 | display: inline-block; 52 | } 53 | 54 | .unblock-user { 55 | margin-left: 34px; 56 | } 57 | 58 | 59 | @media (min-width: 992px) { 60 | .user-enabled { 61 | margin-left: 40px; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/Extensions.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DatingApp.ViewModels; 7 | using Microsoft.AspNetCore.Http; 8 | using Newtonsoft.Json; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | 15 | namespace DatingApp.Helpers 16 | { 17 | public static class Extensions 18 | { 19 | public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages) 20 | { 21 | response.Headers.Add("Pagination", JsonConvert.SerializeObject(new PageHeader(currentPage, itemsPerPage, totalItems, totalPages))); 22 | response.Headers.Add("access-control-expose-headers", "Pagination"); // CORS 23 | } 24 | 25 | public static void AddApplicationError(this HttpResponse response, string message) 26 | { 27 | response.Headers.Add("Application-Error", message); 28 | response.Headers.Add("access-control-expose-headers", "Application-Error");// CORS 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/Product.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace DAL.Models 10 | { 11 | public class Product : AuditableEntity 12 | { 13 | public int Id { get; set; } 14 | public string Name { get; set; } 15 | public string Description { get; set; } 16 | public string Icon { get; set; } 17 | public decimal BuyingPrice { get; set; } 18 | public decimal SellingPrice { get; set; } 19 | public int UnitsInStock { get; set; } 20 | public bool IsActive { get; set; } 21 | public bool IsDiscontinued { get; set; } 22 | public DateTime DateCreated { get; set; } 23 | public DateTime DateModified { get; set; } 24 | 25 | public int? ParentId { get; set; } 26 | public Product Parent { get; set; } 27 | 28 | public int ProductCategoryId { get; set; } 29 | public ProductCategory ProductCategory { get; set; } 30 | 31 | public ICollection Children { get; set; } 32 | public ICollection OrderDetails { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/user.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export class User { 7 | // Note: Using only optional constructor properties without backing store disables typescript's type checking for the type 8 | constructor(id?: string, userName?: string, fullName?: string, email?: string, jobTitle?: string, phoneNumber?: string, roles?: string[]) { 9 | 10 | this.id = id; 11 | this.userName = userName; 12 | this.fullName = fullName; 13 | this.email = email; 14 | this.jobTitle = jobTitle; 15 | this.phoneNumber = phoneNumber; 16 | this.roles = roles; 17 | } 18 | 19 | 20 | get friendlyName(): string { 21 | let name = this.fullName || this.userName; 22 | 23 | if (this.jobTitle) { 24 | name = this.jobTitle + ' ' + name; 25 | } 26 | 27 | return name; 28 | } 29 | 30 | 31 | public id: string; 32 | public userName: string; 33 | public fullName: string; 34 | public email: string; 35 | public jobTitle: string; 36 | public phoneNumber: string; 37 | public isEnabled: boolean; 38 | public isLockedOut: boolean; 39 | public roles: string[]; 40 | } 41 | -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/CustomerRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Microsoft.EntityFrameworkCore; 10 | using DAL.Models; 11 | using DAL.Repositories.Interfaces; 12 | 13 | namespace DAL.Repositories 14 | { 15 | public class CustomerRepository : Repository, ICustomerRepository 16 | { 17 | public CustomerRepository(ApplicationDbContext context) : base(context) 18 | { } 19 | 20 | public IEnumerable GetTopActiveCustomers(int count) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public IEnumerable GetAllCustomersData() 26 | { 27 | return _appContext.Customers 28 | .Include(c => c.Orders).ThenInclude(o => o.OrderDetails).ThenInclude(d => d.Product) 29 | .Include(c => c.Orders).ThenInclude(o => o.Cashier) 30 | .OrderBy(c => c.Name) 31 | .ToList(); 32 | } 33 | 34 | private ApplicationDbContext _appContext => (ApplicationDbContext)_context; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DatingApp/DAL/Core/ProfileConstants.cs: -------------------------------------------------------------------------------- 1 | namespace DAL.Core 2 | { 3 | public static class ClaimConstants 4 | { 5 | ///A claim that specifies the subject of an entity 6 | public const string Subject = "sub"; 7 | 8 | ///A claim that specifies the permission of an entity 9 | public const string Permission = "permission"; 10 | } 11 | 12 | 13 | 14 | public static class PropertyConstants 15 | { 16 | ///A property that specifies the full name of an entity 17 | public const string FullName = "fullname"; 18 | 19 | ///A property that specifies the job title of an entity 20 | public const string JobTitle = "jobtitle"; 21 | 22 | ///A property that specifies the configuration/customizations of an entity 23 | public const string Configuration = "configuration"; 24 | } 25 | 26 | 27 | 28 | public static class ScopeConstants 29 | { 30 | ///A scope that specifies the roles of an entity 31 | public const string Roles = "roles"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # Quickapp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.6. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/CustomerViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using FluentValidation; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | 14 | namespace DatingApp.ViewModels 15 | { 16 | public class CustomerViewModel 17 | { 18 | public int Id { get; set; } 19 | public string Name { get; set; } 20 | public string Email { get; set; } 21 | public string PhoneNumber { get; set; } 22 | public string Address { get; set; } 23 | public string City { get; set; } 24 | public string Gender { get; set; } 25 | 26 | public ICollection Orders { get; set; } 27 | } 28 | 29 | 30 | 31 | 32 | public class CustomerViewModelValidator : AbstractValidator 33 | { 34 | public CustomerViewModelValidator() 35 | { 36 | RuleFor(register => register.Name).NotEmpty().WithMessage("Customer name cannot be empty"); 37 | RuleFor(register => register.Gender).NotEmpty().WithMessage("Gender cannot be empty"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/app-error.handler.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable, ErrorHandler } from '@angular/core'; 7 | import { AlertService, MessageSeverity } from './services/alert.service'; 8 | 9 | 10 | @Injectable() 11 | export class AppErrorHandler extends ErrorHandler { 12 | 13 | // private alertService: AlertService; 14 | 15 | constructor() { 16 | super(); 17 | } 18 | 19 | 20 | handleError(error: any) { 21 | // if (this.alertService == null) { 22 | // this.alertService = this.injector.get(AlertService); 23 | // } 24 | 25 | // this.alertService.showStickyMessage("Fatal Error!", "An unresolved error has occured. Please reload the page to correct this error", MessageSeverity.warn); 26 | // this.alertService.showStickyMessage("Unhandled Error", error.message || error, MessageSeverity.error, error); 27 | 28 | if (confirm('Fatal Error!\nAn unresolved error has occured. Do you want to reload the page to correct this?\n\nError: ' + error.message)) { 29 | window.location.reload(true); 30 | } 31 | 32 | super.handleError(error); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/themes/solar.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/custom-vendor'; 2 | 3 | @import 'node_modules/bootswatch/dist/solar/variables'; 4 | @import 'node_modules/bootstrap/scss/bootstrap'; 5 | @import 'node_modules/bootswatch/dist/solar/bootswatch'; 6 | 7 | @import "app-theme"; 8 | 9 | 10 | .bootstrap-select { 11 | .dropdown-toggle.btn-light { 12 | background-color: $input-bg; 13 | } 14 | 15 | & > .dropdown-menu > .dropdown-menu li a { 16 | color: $body-color; 17 | } 18 | } 19 | 20 | 21 | .ngx-datatable.material { 22 | &.table-striped { 23 | .datatable-row-even { 24 | background: $dark; 25 | } 26 | } 27 | } 28 | 29 | 30 | .dropdown-menu { 31 | border-color: $body-color; 32 | } 33 | 34 | 35 | .popover-header { 36 | color: $body-color; 37 | } 38 | 39 | 40 | .bg-light { 41 | background-color: #04272f !important; 42 | } 43 | 44 | 45 | .app-component { 46 | footer { 47 | background-color: #073642 !important; 48 | } 49 | } 50 | 51 | .about-page { 52 | .bg-features { 53 | .logo { 54 | color: #073642 !important; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/models/permission.model.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export type PermissionNames = 7 | 'View Users' | 'Manage Users' | 8 | 'View Roles' | 'Manage Roles' | 'Assign Roles'; 9 | 10 | export type PermissionValues = 11 | 'users.view' | 'users.manage' | 12 | 'roles.view' | 'roles.manage' | 'roles.assign'; 13 | 14 | export class Permission { 15 | 16 | public static readonly viewUsersPermission: PermissionValues = 'users.view'; 17 | public static readonly manageUsersPermission: PermissionValues = 'users.manage'; 18 | 19 | public static readonly viewRolesPermission: PermissionValues = 'roles.view'; 20 | public static readonly manageRolesPermission: PermissionValues = 'roles.manage'; 21 | public static readonly assignRolesPermission: PermissionValues = 'roles.assign'; 22 | 23 | 24 | constructor(name?: PermissionNames, value?: PermissionValues, groupName?: string, description?: string) { 25 | this.name = name; 26 | this.value = value; 27 | this.groupName = groupName; 28 | this.description = description; 29 | } 30 | 31 | public name: PermissionNames; 32 | public value: PermissionValues; 33 | public groupName: string; 34 | public description: string; 35 | } 36 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home Page - DatingApp 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

19 | App is Loading... 20 |

21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |

32 | DATING APPLICATION 33 |

34 | 35 |
36 |
37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // Karma configuration file, see link for more information 7 | // https://karma-runner.github.io/1.0/config/configuration-file.html 8 | 9 | module.exports = function (config) { 10 | config.set({ 11 | basePath: '', 12 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 13 | plugins: [ 14 | require('karma-jasmine'), 15 | require('karma-chrome-launcher'), 16 | require('karma-jasmine-html-reporter'), 17 | require('karma-coverage-istanbul-reporter'), 18 | require('@angular-devkit/build-angular/plugins/karma') 19 | ], 20 | client: { 21 | clearContext: false // leave Jasmine Spec Runner output visible in browser 22 | }, 23 | coverageIstanbulReporter: { 24 | dir: require('path').join(__dirname, '../coverage'), 25 | reports: ['html', 'lcovonly'], 26 | fixWebpackSourcePaths: true 27 | }, 28 | reporters: ['progress', 'kjhtml'], 29 | port: 9876, 30 | colors: true, 31 | logLevel: config.LOG_INFO, 32 | autoWatch: true, 33 | browsers: ['Chrome'], 34 | singleRun: false 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/DesignTimeDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using DAL; 12 | using Microsoft.EntityFrameworkCore.Design; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.EntityFrameworkCore; 15 | using AutoMapper; 16 | 17 | namespace DatingApp 18 | { 19 | public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory 20 | { 21 | public ApplicationDbContext CreateDbContext(string[] args) 22 | { 23 | Mapper.Reset(); 24 | 25 | IConfigurationRoot configuration = new ConfigurationBuilder() 26 | .SetBasePath(Directory.GetCurrentDirectory()) 27 | .AddJsonFile("appsettings.json") 28 | .AddJsonFile("appsettings.Development.json", optional: true) 29 | .Build(); 30 | 31 | var builder = new DbContextOptionsBuilder(); 32 | 33 | builder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"], b => b.MigrationsAssembly("DatingApp")); 34 | 35 | return new ApplicationDbContext(builder.Options); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, NavigationExtras, CanLoad, Route } from '@angular/router'; 8 | import { AuthService } from './auth.service'; 9 | 10 | 11 | @Injectable() 12 | export class AuthGuard implements CanActivate, CanActivateChild, CanLoad { 13 | constructor(private authService: AuthService, private router: Router) { } 14 | 15 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 16 | 17 | const url: string = state.url; 18 | return this.checkLogin(url); 19 | } 20 | 21 | canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 22 | return this.canActivate(route, state); 23 | } 24 | 25 | canLoad(route: Route): boolean { 26 | 27 | const url = `/${route.path}`; 28 | return this.checkLogin(url); 29 | } 30 | 31 | checkLogin(url: string): boolean { 32 | 33 | if (this.authService.isLoggedIn) { 34 | return true; 35 | } 36 | 37 | this.authService.loginRedirectUrl = url; 38 | this.router.navigate(['/login']); 39 | 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/vertical-tabs.scss: -------------------------------------------------------------------------------- 1 | $padding-base: 15px; 2 | $color-body: transparent; 3 | $color-border: #ddd; 4 | $color-right-border: white; 5 | $border-radius: .25rem; 6 | 7 | .nav-tabs { 8 | &--vertical { 9 | border-bottom: none !important; 10 | border-right: 1px solid $color-border; 11 | display: flex; 12 | flex-flow: column nowrap; 13 | } 14 | 15 | &--left { 16 | margin: 0 $padding-base; 17 | 18 | .nav-item + .nav-item { 19 | margin-top: .25rem; 20 | } 21 | 22 | .nav-link { 23 | border-radius: $border-radius 0 0 $border-radius !important; 24 | transition: border-color .125s ease-in; 25 | white-space: nowrap; 26 | 27 | &:hover { 28 | background-color: lighten($color-border, 10%); 29 | border-color: transparent; 30 | } 31 | } 32 | 33 | .nav-link.active { 34 | border-color: $color-border !important; 35 | border-right-color: $color-right-border !important; 36 | margin-right: -1px; 37 | 38 | &:hover { 39 | cursor: default; 40 | background-color: $color-body; 41 | border-color: $color-border $color-right-border $color-border $color-border; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/AuthorizeCheckOperationFilter.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using Microsoft.AspNetCore.Authorization; 7 | using Swashbuckle.AspNetCore.Swagger; 8 | using Swashbuckle.AspNetCore.SwaggerGen; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Reflection; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace DatingApp 17 | { 18 | // Swagger IOperationFilter implementation that will decide which api action needs authorization 19 | internal class AuthorizeCheckOperationFilter : IOperationFilter 20 | { 21 | public void Apply(Operation operation, OperationFilterContext context) 22 | { 23 | // Check for authorize attribute 24 | var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true) 25 | .Union(context.MethodInfo.GetCustomAttributes(true)) 26 | .OfType() 27 | .Any(); 28 | 29 | if (hasAuthorize) 30 | { 31 | operation.Responses.Add("401", new Response { Description = "Unauthorized" }); 32 | 33 | operation.Security = new List>> 34 | { 35 | new Dictionary> 36 | { 37 | { "oauth2", new [] { IdentityServerConfig.ApiName} } 38 | } 39 | }; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/app.component.scss: -------------------------------------------------------------------------------- 1 | $footerHeight: 40px; 2 | 3 | .app-container.app-component { 4 | height: 100%; 5 | } 6 | 7 | main.app-component { 8 | height: calc(100% - #{$footerHeight}); 9 | padding-top: 60px; 10 | } 11 | 12 | .footer-height.app-component { 13 | height: $footerHeight; 14 | } 15 | 16 | footer.app-component { 17 | width: 100%; 18 | height: $footerHeight; 19 | line-height: $footerHeight; 20 | background-color: #f5f5f5; 21 | } 22 | 23 | 24 | .navbar-brand.app-component > img { 25 | height: 30px; 26 | } 27 | 28 | .navbar.app-component .nav-pills > .active > a, 29 | .navbar.app-component .nav-pills > .active > a:hover, 30 | .navbar.app-component .nav-pills > .active > a:focus { 31 | background-color: #efefef; 32 | color: #808080; 33 | } 34 | 35 | 36 | .navbar.app-component .nav-pills > li > a { 37 | font-weight: bold; 38 | padding-top: 0.4rem; 39 | padding-bottom: 0.4rem; 40 | } 41 | 42 | .navbar.app-component .nav-link, 43 | .navbar.app-component .navbar-text { 44 | color: white; 45 | } 46 | 47 | 48 | @media screen and (max-width: 991px) { 49 | .navbar.app-component .nav-pills > li + li { 50 | margin-left: 0; 51 | margin-top: 1px; 52 | } 53 | } 54 | 55 | @media screen and (min-width: 992px) { 56 | .navbar.app-component .nav-pills > li + li { 57 | margin-left: 2px; 58 | } 59 | } 60 | 61 | 62 | .prebootShow.app-component { 63 | opacity: 1 !important; 64 | } 65 | 66 | .prebootStep.app-component { 67 | opacity: 0; 68 | transition: .5s ease-in-out all; 69 | } 70 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"d030c0ba9d6698d85624b355d4be1ada","Parameters":{"D":"PKDUgxE/+fsyNJ8VEWbVhVEdkQ8ETm+PwfN83OcJ8jcj9+4f0N2XPI3ZsQuWAxS9AhQAwnq4TFRsD3aP99ITTRpJxErY61Osw/KuG1Y0faC0qvvouH28S7aulW1oUagHfVH/aU6QJlQqRX5IjztjjmSTmePtSRNbXZFXLJ889pKH+IL1aMwbKArHDZFWE599lY7eBL3iyNTnp7KEuBoyFKRMWrE/kQZ5tWeZM6mC9KThu1/7azORp/bV7wcey4w9L873dR6VfMrrTLrhGlh8FqPoSuANBBY4Kfa/EWAhdHUwamom81VDqwG3TJUlK70d3K22zx8qri+V9Lnhr0jrnQ==","DP":"YsRzPctDaaqc/TkKHIS9pp0eFDlXC2CkOOex0QHFJ1DffJCXP3quDRBeIjUIyT3laUhVZAdjej59N6GCbcg25zwmSK1quOkMsmBa3ETJnvzgJK4/36pC8rQT23mEkB+s4HKIuRJXuAHwur8EBGChWiDitxc2ohGx0K0/fhnGL7M=","DQ":"BP6kFbMPqwfv6/miVRIY8hBkYpuioFhmwzpbvd1yf7zN2oElsl9daQDKILWbKIASFb2xmaR+N6UqY9zoDdqpjhTMlSWUGlGjDjUk1WkPQRj67ua9KWogJetbsiOBbaT5YEhZmsQJV2yAx4Flydpd5VawTSYGr+olSftp7FhJgJ8=","Exponent":"AQAB","InverseQ":"iymzp/ERuEVk3MDmVEKaaElZ8URSf4MWD6tlhRTDstq+SxgVGtD8UjcFi+xWyslap2I9frPD0skWBopk41eSzxCSbgeSMkdmeR9otekZCXRaw8WDuB0Hk+Xxfz8GU0wWE6uo1CaCQUJqkxtlEre4KUgy6kiOVlDn0FL0J7C8BrM=","Modulus":"nlWEtPJ0BWC4RfPrJ1Mjp6S1PKb83NXCN2KP+/WVPyWDBn/HMHCKg4u6lFwBaiGQHwWmW0FD5mHAkUrXNYv2UiLbNcbn5XEpymWlSnEMX1Uh4AkDzyQHUaFLdavRGwbQ37U2Rg/Tp3TcfGuAS8Y1NRlJ7uCkqH16zTxT1Ve3WErtSiu9XxLgP0abhxi/ZftqNWKRG9+Rqg1dfypdYyAjMEDcGM6JdeF73hZEk7QfwaN2qGYgFyQqjAQam+OJEsr7GF/mUuX9c4vWrN2n2H0XIIkzJLzjKiKlv1a+YnfQ/7F7oneeo2jGvdaGSc4bjW0DBweFEB/N2OzXPpeMwS4OoQ==","P":"0qGs7okT4ClfvUYteuNadDK4j89x0tSJh8JPujAvICFgySDtphknW9h5YMRJudSwB7Y3Ih6I8qmvS+uoZGLWRMi9o5SiKUzLUomcbJdtngGCRk+539iRuPeh9pkg+hRARytrUQ4LEMyqi25okvxw0OCPWTWJb7zbPcMBr4XQFFc=","Q":"wHAiXXt3DyQc4fvVECsLnN8bsSQnup+ySU/CoTavqTUQmfYY6SIhd2BW6dYkIszEhjZhuZSFqh9PKslbKrMBd2ZKCaf+x9MOkzf9+fbE4JSyxpw6ar9eq8veg2qjnaI95N5r7Pum0q0a8NN/DzlNdof/X53ZdpGQWTObbgwEWcc="}} -------------------------------------------------------------------------------- /DatingApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.645 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatingApp", "DatingApp\DatingApp\DatingApp.csproj", "{7AC09AE0-255F-4A62-9C1D-E2109645868F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DAL", "DatingApp\DAL\DAL.csproj", "{C64FA28F-790C-4A77-9404-D59A23E1399A}" 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 | {7AC09AE0-255F-4A62-9C1D-E2109645868F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {7AC09AE0-255F-4A62-9C1D-E2109645868F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {7AC09AE0-255F-4A62-9C1D-E2109645868F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {7AC09AE0-255F-4A62-9C1D-E2109645868F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C64FA28F-790C-4A77-9404-D59A23E1399A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C64FA28F-790C-4A77-9404-D59A23E1399A}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C64FA28F-790C-4A77-9404-D59A23E1399A}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C64FA28F-790C-4A77-9404-D59A23E1399A}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1D4A0E94-E54E-4BB8-BABE-034450E0CCCA} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DatingApp/DAL/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using DAL.Repositories; 12 | using DAL.Repositories.Interfaces; 13 | 14 | namespace DAL 15 | { 16 | public class UnitOfWork : IUnitOfWork 17 | { 18 | readonly ApplicationDbContext _context; 19 | 20 | ICustomerRepository _customers; 21 | IProductRepository _products; 22 | IOrdersRepository _orders; 23 | 24 | 25 | 26 | public UnitOfWork(ApplicationDbContext context) 27 | { 28 | _context = context; 29 | } 30 | 31 | 32 | 33 | public ICustomerRepository Customers 34 | { 35 | get 36 | { 37 | if (_customers == null) 38 | _customers = new CustomerRepository(_context); 39 | 40 | return _customers; 41 | } 42 | } 43 | 44 | 45 | 46 | public IProductRepository Products 47 | { 48 | get 49 | { 50 | if (_products == null) 51 | _products = new ProductRepository(_context); 52 | 53 | return _products; 54 | } 55 | } 56 | 57 | 58 | 59 | public IOrdersRepository Orders 60 | { 61 | get 62 | { 63 | if (_orders == null) 64 | _orders = new OrdersRepository(_context); 65 | 66 | return _orders; 67 | } 68 | } 69 | 70 | 71 | 72 | 73 | public int SaveChanges() 74 | { 75 | return _context.SaveChanges(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/equal-validator.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, forwardRef, Attribute } from '@angular/core'; 7 | import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms'; 8 | 9 | 10 | 11 | @Directive({ 12 | selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]', 13 | providers: [ 14 | { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true } 15 | ] 16 | }) 17 | export class EqualValidator implements Validator { 18 | constructor( @Attribute('validateEqual') public validateEqual: string, 19 | @Attribute('reverse') public reverse: string) { 20 | } 21 | 22 | validate(c: AbstractControl): { [key: string]: any } { 23 | const other = c.root.get(this.validateEqual); 24 | 25 | if (!other) { 26 | return null; 27 | } 28 | 29 | return this.reverse === 'true' ? this.validateReverse(c, other) : this.validateNoReverse(c, other); 30 | } 31 | 32 | private validateNoReverse(c: AbstractControl, other: AbstractControl): { [key: string]: any } { 33 | return other.value === c.value ? null : { validateEqual: true }; 34 | } 35 | 36 | private validateReverse(c: AbstractControl, other: AbstractControl): { [key: string]: any } { 37 | if (c.value === other.value) { 38 | if (other.errors) { 39 | delete other.errors['validateEqual']; 40 | 41 | if (Object.keys(other.errors).length == 0) { 42 | other.setErrors(null); 43 | } 44 | } 45 | } else { 46 | other.setErrors({ validateEqual: true }); 47 | } 48 | 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/app-title.service.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { Router, NavigationEnd, PRIMARY_OUTLET } from '@angular/router'; 8 | import { Subscription } from 'rxjs'; 9 | import { filter, map, flatMap } from 'rxjs/operators'; 10 | import { Title } from '@angular/platform-browser'; 11 | 12 | import { Utilities } from './utilities'; 13 | 14 | 15 | @Injectable() 16 | export class AppTitleService { 17 | 18 | sub: Subscription; 19 | appName: string; 20 | 21 | constructor(private titleService: Title, private router: Router) { 22 | this.sub = this.router.events.pipe( 23 | filter(event => event instanceof NavigationEnd), 24 | map(_ => this.router.routerState.root), 25 | map(route => { 26 | while (route.firstChild) { 27 | route = route.firstChild; 28 | } 29 | 30 | return route; 31 | }), 32 | flatMap(route => route.data)) 33 | .subscribe(data => { 34 | let title = data['title']; 35 | 36 | if (title) { 37 | const fragment = this.router.url.split('#')[1]; 38 | 39 | if (fragment) { 40 | title += ' | ' + Utilities.toTitleCase(fragment); 41 | } 42 | } 43 | 44 | if (title && this.appName) { 45 | title += ' - ' + this.appName; 46 | } else if (this.appName) { 47 | title = this.appName; 48 | } 49 | 50 | if (title) { 51 | this.titleService.setTitle(title); 52 | } 53 | }); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/bootstrap-tab.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, ElementRef, Output, EventEmitter, OnDestroy, NgZone } from '@angular/core'; 7 | import { Observable, Subscription, fromEvent } from 'rxjs'; 8 | 9 | 10 | declare var $: any; 11 | 12 | interface eventArg { type: string; target: Element; relatedTarget: Element; } 13 | 14 | @Directive({ 15 | selector: '[bootstrapTab]', 16 | exportAs: 'bootstrap-tab' 17 | }) 18 | export class BootstrapTabDirective implements OnDestroy { 19 | 20 | 21 | @Output() 22 | showBSTab = new EventEmitter(); 23 | 24 | @Output() 25 | hideBSTab = new EventEmitter(); 26 | 27 | private tabShownSubscription: Subscription; 28 | private tabHiddenSubscription: Subscription; 29 | 30 | 31 | constructor(private el: ElementRef, private zone: NgZone) { 32 | 33 | this.tabShownSubscription = fromEvent($(this.el.nativeElement), 'show.bs.tab') 34 | .subscribe((e: any) => { 35 | this.runInZone(() => this.showBSTab.emit({ type: e.type, target: e.target, relatedTarget: e.relatedTarget })); 36 | }); 37 | 38 | this.tabHiddenSubscription = fromEvent($(this.el.nativeElement), 'hidden.bs.tab') 39 | .subscribe((e: any) => { 40 | this.runInZone(() => this.hideBSTab.emit({ type: e.type, target: e.target, relatedTarget: e.relatedTarget })); 41 | }); 42 | } 43 | 44 | 45 | ngOnDestroy() { 46 | this.tabShownSubscription.unsubscribe(); 47 | this.tabHiddenSubscription.unsubscribe(); 48 | } 49 | 50 | 51 | private runInZone(delegate: () => any) { 52 | this.zone.run(() => { 53 | delegate(); 54 | }); 55 | } 56 | 57 | 58 | show(selector: string) { 59 | $(selector).tab('show'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using Microsoft.AspNetCore.Identity; 9 | using DAL.Models.Interfaces; 10 | 11 | namespace DAL.Models 12 | { 13 | public class ApplicationUser : IdentityUser, IAuditableEntity 14 | { 15 | public virtual string FriendlyName 16 | { 17 | get 18 | { 19 | string friendlyName = string.IsNullOrWhiteSpace(FullName) ? UserName : FullName; 20 | 21 | if (!string.IsNullOrWhiteSpace(JobTitle)) 22 | friendlyName = $"{JobTitle} {friendlyName}"; 23 | 24 | return friendlyName; 25 | } 26 | } 27 | 28 | public string JobTitle { get; set; } 29 | public string FullName { get; set; } 30 | public string Configuration { get; set; } 31 | public bool IsEnabled { get; set; } 32 | public bool IsLockedOut => this.LockoutEnabled && this.LockoutEnd >= DateTimeOffset.UtcNow; 33 | public string CreatedBy { get; set; } 34 | public string UpdatedBy { get; set; } 35 | public DateTime CreatedDate { get; set; } 36 | public DateTime UpdatedDate { get; set; } 37 | 38 | /// 39 | /// Navigation property for the roles this user belongs to. 40 | /// 41 | public virtual ICollection> Roles { get; set; } 42 | 43 | /// 44 | /// Navigation property for the claims this user possesses. 45 | /// 46 | public virtual ICollection> Claims { get; set; } 47 | 48 | /// 49 | /// Demo Navigation property for orders this user has processed 50 | /// 51 | public ICollection Orders { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/MinimumCountAttribute.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.ComponentModel.DataAnnotations; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace DatingApp.Helpers 15 | { 16 | [AttributeUsage(AttributeTargets.Property)] 17 | public sealed class MinimumCountAttribute : ValidationAttribute 18 | { 19 | private readonly int _minCount; 20 | private readonly bool _allowEmptyStringValues; 21 | private readonly bool _required; 22 | private const string _defaultError = "'{0}' must have at least {1} item."; 23 | 24 | public MinimumCountAttribute() : this(1) 25 | { 26 | 27 | } 28 | 29 | public MinimumCountAttribute(int minCount, bool required = true, bool allowEmptyStringValues = false) : base(_defaultError) 30 | { 31 | _minCount = minCount; 32 | _required = required; 33 | _allowEmptyStringValues = allowEmptyStringValues; 34 | } 35 | 36 | public override bool IsValid(object value) 37 | { 38 | if (value == null) 39 | return !_required; 40 | 41 | 42 | var stringList = value as ICollection; 43 | if (!_allowEmptyStringValues && stringList != null) 44 | return stringList.Count(s => !string.IsNullOrWhiteSpace(s)) >= _minCount; 45 | 46 | 47 | var list = value as ICollection; 48 | if (list != null) 49 | return list.Count >= _minCount; 50 | 51 | 52 | return false; 53 | } 54 | 55 | 56 | public override string FormatErrorMessage(string name) 57 | { 58 | return String.Format(this.ErrorMessageString, name, _minCount); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/banner-demo.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | ASP.NET 6 | 14 | 15 | 16 | Visual Studio 17 | 25 | 26 | 27 | Package Management 28 | 36 | 37 | 38 | Eben Monney 39 | 43 | 44 | 45 |
46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/bootstrap-toggle.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, ElementRef, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; 7 | import { Observable, Subscription, fromEvent } from 'rxjs'; 8 | 9 | 10 | declare var $: any; 11 | 12 | @Directive({ 13 | selector: '[bootstrapToggle]', 14 | exportAs: 'bootstrap-toggle' 15 | }) 16 | export class BootstrapToggleDirective implements OnInit, OnDestroy { 17 | 18 | private checkedSubscription: Subscription; 19 | 20 | @Input() 21 | set ngModel(value) { 22 | this.toggle(value); 23 | } 24 | 25 | @Output() 26 | ngModelChange = new EventEmitter(); 27 | 28 | 29 | constructor(private el: ElementRef) { 30 | this.checkedSubscription = fromEvent($(this.el.nativeElement), 'change') 31 | .subscribe((e: any) => this.ngModelChange.emit(e.target.checked)); 32 | } 33 | 34 | 35 | ngOnInit() { 36 | this.initialize(); 37 | } 38 | 39 | ngOnDestroy() { 40 | this.destroy(); 41 | } 42 | 43 | 44 | initialize(options?: any) { 45 | $(this.el.nativeElement).bootstrapToggle(options); 46 | } 47 | 48 | destroy() { 49 | if (this.checkedSubscription) { 50 | this.checkedSubscription.unsubscribe(); 51 | } 52 | 53 | $(this.el.nativeElement).bootstrapToggle('destroy'); 54 | } 55 | 56 | toggleOn() { 57 | $(this.el.nativeElement).bootstrapToggle('on'); 58 | } 59 | 60 | toggleOff() { 61 | $(this.el.nativeElement).bootstrapToggle('off'); 62 | } 63 | 64 | toggle(value?: boolean) { 65 | if (value == null) { 66 | $(this.el.nativeElement).bootstrapToggle('toggle'); 67 | } else { 68 | $(this.el.nativeElement).prop('checked', value).change(); 69 | } 70 | } 71 | 72 | enable() { 73 | $(this.el.nativeElement).bootstrapToggle('enable'); 74 | } 75 | 76 | disable() { 77 | $(this.el.nativeElement).bootstrapToggle('disable'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/notifications-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{getPrintedDate(value)}} 25 | 26 | 27 | 28 | 29 | 30 | {{value}} 31 | 32 | 33 | 34 | 35 | 36 | {{value}} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
-------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/Utilities.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using IdentityModel; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Security.Claims; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace DatingApp.Helpers 17 | { 18 | public static class Utilities 19 | { 20 | static ILoggerFactory _loggerFactory; 21 | 22 | 23 | public static void ConfigureLogger(ILoggerFactory loggerFactory) 24 | { 25 | _loggerFactory = loggerFactory; 26 | } 27 | 28 | 29 | public static ILogger CreateLogger() 30 | { 31 | //Usage: Utilities.CreateLogger().LogError(LoggingEvents.SomeEventId, ex, "An error occurred because of xyz"); 32 | 33 | if (_loggerFactory == null) 34 | { 35 | throw new InvalidOperationException($"{nameof(ILogger)} is not configured. {nameof(ConfigureLogger)} must be called before use"); 36 | //_loggerFactory = new LoggerFactory().AddConsole().AddDebug(); 37 | } 38 | 39 | return _loggerFactory.CreateLogger(); 40 | } 41 | 42 | 43 | public static void QuickLog(string text, string filename) 44 | { 45 | string dirPath = Path.GetDirectoryName(filename); 46 | 47 | if (!Directory.Exists(dirPath)) 48 | Directory.CreateDirectory(dirPath); 49 | 50 | using (StreamWriter writer = File.AppendText(filename)) 51 | { 52 | writer.WriteLine($"{DateTime.Now} - {text}"); 53 | } 54 | } 55 | 56 | 57 | 58 | public static string GetUserId(ClaimsPrincipal user) 59 | { 60 | return user.FindFirst(JwtClaimTypes.Subject)?.Value?.Trim(); 61 | } 62 | 63 | 64 | 65 | public static string[] GetRoles(ClaimsPrincipal identity) 66 | { 67 | return identity.Claims 68 | .Where(c => c.Type == JwtClaimTypes.Role) 69 | .Select(c => c.Value) 70 | .ToArray(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Program.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using DAL; 12 | using Microsoft.AspNetCore; 13 | using Microsoft.AspNetCore.Hosting; 14 | using Microsoft.Extensions.Configuration; 15 | using Microsoft.Extensions.DependencyInjection; 16 | using Microsoft.Extensions.Logging; 17 | using DatingApp.Helpers; 18 | 19 | namespace DatingApp 20 | { 21 | public class Program 22 | { 23 | public static void Main(string[] args) 24 | { 25 | var host = CreateWebHostBuilder(args).Build(); 26 | 27 | 28 | //Seed database 29 | using (var scope = host.Services.CreateScope()) 30 | { 31 | var services = scope.ServiceProvider; 32 | 33 | try 34 | { 35 | var databaseInitializer = services.GetRequiredService(); 36 | databaseInitializer.SeedAsync().Wait(); 37 | } 38 | catch (Exception ex) 39 | { 40 | var logger = services.GetRequiredService>(); 41 | logger.LogCritical(LoggingEvents.INIT_DATABASE, ex, LoggingEvents.INIT_DATABASE.Name); 42 | 43 | throw new Exception(LoggingEvents.INIT_DATABASE.Name, ex); 44 | } 45 | } 46 | 47 | host.Run(); 48 | } 49 | 50 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 51 | WebHost.CreateDefaultBuilder(args) 52 | .UseStartup() 53 | .ConfigureLogging((hostingContext, logging) => 54 | { 55 | logging.ClearProviders(); 56 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 57 | logging.AddConsole(); 58 | logging.AddDebug(); 59 | logging.AddEventSourceLogger(); 60 | logging.AddFile(hostingContext.Configuration.GetSection("Logging")); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Authorization/ProfileService.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Security.Claims; 10 | using System.Threading.Tasks; 11 | using IdentityModel; 12 | using IdentityServer4.Extensions; 13 | using IdentityServer4.Models; 14 | using IdentityServer4.Services; 15 | using Microsoft.AspNetCore.Identity; 16 | using DAL.Core; 17 | using DAL.Models; 18 | 19 | namespace DatingApp.Authorization 20 | { 21 | public class ProfileService : IProfileService 22 | { 23 | private readonly UserManager _userManager; 24 | private readonly IUserClaimsPrincipalFactory _claimsFactory; 25 | 26 | public ProfileService(UserManager userManager, IUserClaimsPrincipalFactory claimsFactory) 27 | { 28 | _userManager = userManager; 29 | _claimsFactory = claimsFactory; 30 | } 31 | 32 | public async Task GetProfileDataAsync(ProfileDataRequestContext context) 33 | { 34 | var sub = context.Subject.GetSubjectId(); 35 | var user = await _userManager.FindByIdAsync(sub); 36 | var principal = await _claimsFactory.CreateAsync(user); 37 | 38 | var claims = principal.Claims.ToList(); 39 | claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); 40 | 41 | if (user.JobTitle != null) 42 | claims.Add(new Claim(PropertyConstants.JobTitle, user.JobTitle)); 43 | 44 | if (user.FullName != null) 45 | claims.Add(new Claim(PropertyConstants.FullName, user.FullName)); 46 | 47 | if (user.Configuration != null) 48 | claims.Add(new Claim(PropertyConstants.Configuration, user.Configuration)); 49 | 50 | context.IssuedClaims = claims; 51 | } 52 | 53 | 54 | public async Task IsActiveAsync(IsActiveContext context) 55 | { 56 | var sub = context.Subject.GetSubjectId(); 57 | var user = await _userManager.FindByIdAsync(sub); 58 | 59 | context.IsActive = (user != null) && user.IsEnabled; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /DatingApp/DAL/Repositories/Repository.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Repositories.Interfaces; 7 | using Microsoft.EntityFrameworkCore; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Linq.Expressions; 12 | 13 | namespace DAL.Repositories 14 | { 15 | public class Repository : IRepository where TEntity : class 16 | { 17 | protected readonly DbContext _context; 18 | protected readonly DbSet _entities; 19 | 20 | public Repository(DbContext context) 21 | { 22 | _context = context; 23 | _entities = context.Set(); 24 | } 25 | 26 | public virtual void Add(TEntity entity) 27 | { 28 | _entities.Add(entity); 29 | } 30 | 31 | public virtual void AddRange(IEnumerable entities) 32 | { 33 | _entities.AddRange(entities); 34 | } 35 | 36 | 37 | public virtual void Update(TEntity entity) 38 | { 39 | _entities.Update(entity); 40 | } 41 | 42 | public virtual void UpdateRange(IEnumerable entities) 43 | { 44 | _entities.UpdateRange(entities); 45 | } 46 | 47 | public virtual void Remove(TEntity entity) 48 | { 49 | _entities.Remove(entity); 50 | } 51 | 52 | public virtual void RemoveRange(IEnumerable entities) 53 | { 54 | _entities.RemoveRange(entities); 55 | } 56 | 57 | public virtual int Count() 58 | { 59 | return _entities.Count(); 60 | } 61 | 62 | public virtual IEnumerable Find(Expression> predicate) 63 | { 64 | return _entities.Where(predicate); 65 | } 66 | 67 | public virtual TEntity GetSingleOrDefault(Expression> predicate) 68 | { 69 | return _entities.SingleOrDefault(predicate); 70 | } 71 | 72 | public virtual TEntity Get(int id) 73 | { 74 | return _entities.Find(id); 75 | } 76 | 77 | public virtual IEnumerable GetAll() 78 | { 79 | return _entities.ToList(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/AutoMapperProfile.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using AutoMapper; 7 | using DAL.Core; 8 | using DAL.Models; 9 | using Microsoft.AspNetCore.Identity; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace DatingApp.ViewModels 17 | { 18 | public class AutoMapperProfile : Profile 19 | { 20 | public AutoMapperProfile() 21 | { 22 | CreateMap() 23 | .ForMember(d => d.Roles, map => map.Ignore()); 24 | CreateMap() 25 | .ForMember(d => d.Roles, map => map.Ignore()); 26 | 27 | CreateMap() 28 | .ForMember(d => d.Roles, map => map.Ignore()); 29 | CreateMap() 30 | .ForMember(d => d.Roles, map => map.Ignore()); 31 | 32 | CreateMap() 33 | .ReverseMap(); 34 | 35 | CreateMap() 36 | .ForMember(d => d.Permissions, map => map.MapFrom(s => s.Claims)) 37 | .ForMember(d => d.UsersCount, map => map.MapFrom(s => s.Users != null ? s.Users.Count : 0)) 38 | .ReverseMap(); 39 | CreateMap(); 40 | 41 | CreateMap, ClaimViewModel>() 42 | .ForMember(d => d.Type, map => map.MapFrom(s => s.ClaimType)) 43 | .ForMember(d => d.Value, map => map.MapFrom(s => s.ClaimValue)) 44 | .ReverseMap(); 45 | 46 | CreateMap() 47 | .ReverseMap(); 48 | 49 | CreateMap, PermissionViewModel>() 50 | .ConvertUsing(s => Mapper.Map(ApplicationPermissions.GetPermissionByValue(s.ClaimValue))); 51 | 52 | CreateMap() 53 | .ReverseMap(); 54 | 55 | CreateMap() 56 | .ReverseMap(); 57 | 58 | CreateMap() 59 | .ReverseMap(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Helpers/EmailTemplates.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using Microsoft.Extensions.FileProviders; 8 | using Microsoft.AspNetCore.Hosting; 9 | using System.IO; 10 | 11 | namespace DatingApp.Helpers 12 | { 13 | public static class EmailTemplates 14 | { 15 | static IHostingEnvironment _hostingEnvironment; 16 | static string testEmailTemplate; 17 | static string plainTextTestEmailTemplate; 18 | 19 | 20 | public static void Initialize(IHostingEnvironment hostingEnvironment) 21 | { 22 | _hostingEnvironment = hostingEnvironment; 23 | } 24 | 25 | 26 | public static string GetTestEmail(string recepientName, DateTime testDate) 27 | { 28 | if (testEmailTemplate == null) 29 | testEmailTemplate = ReadPhysicalFile("Helpers/Templates/TestEmail.template"); 30 | 31 | 32 | string emailMessage = testEmailTemplate 33 | .Replace("{user}", recepientName) 34 | .Replace("{testDate}", testDate.ToString()); 35 | 36 | return emailMessage; 37 | } 38 | 39 | 40 | 41 | public static string GetPlainTextTestEmail(DateTime date) 42 | { 43 | if (plainTextTestEmailTemplate == null) 44 | plainTextTestEmailTemplate = ReadPhysicalFile("Helpers/Templates/PlainTextTestEmail.template"); 45 | 46 | 47 | string emailMessage = plainTextTestEmailTemplate 48 | .Replace("{date}", date.ToString()); 49 | 50 | return emailMessage; 51 | } 52 | 53 | 54 | 55 | 56 | private static string ReadPhysicalFile(string path) 57 | { 58 | if (_hostingEnvironment == null) 59 | throw new InvalidOperationException($"{nameof(EmailTemplates)} is not initialized"); 60 | 61 | IFileInfo fileInfo = _hostingEnvironment.ContentRootFileProvider.GetFileInfo(path); 62 | 63 | if (!fileInfo.Exists) 64 | throw new FileNotFoundException($"Template file located at \"{path}\" was not found"); 65 | 66 | using (var fs = fileInfo.CreateReadStream()) 67 | { 68 | using (var sr = new StreamReader(fs)) 69 | { 70 | return sr.ReadToEnd(); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/jwt-helper.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /** 7 | * Helper class to decode and find JWT expiration. 8 | */ 9 | import { Injectable } from '@angular/core'; 10 | 11 | 12 | @Injectable() 13 | export class JwtHelper { 14 | 15 | public urlBase64Decode(str: string): string { 16 | let output = str.replace(/-/g, '+').replace(/_/g, '/'); 17 | switch (output.length % 4) { 18 | case 0: { break; } 19 | case 2: { output += '=='; break; } 20 | case 3: { output += '='; break; } 21 | default: { 22 | throw new Error('Illegal base64url string!'); 23 | } 24 | } 25 | return this.b64DecodeUnicode(output); 26 | } 27 | 28 | // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem 29 | private b64DecodeUnicode(str: any) { 30 | return decodeURIComponent(Array.prototype.map.call(atob(str), (c: any) => { 31 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 32 | }).join('')); 33 | } 34 | 35 | public decodeToken(token: string): any { 36 | const parts = token.split('.'); 37 | 38 | if (parts.length !== 3) { 39 | throw new Error('JWT must have 3 parts'); 40 | } 41 | 42 | const decoded = this.urlBase64Decode(parts[1]); 43 | if (!decoded) { 44 | throw new Error('Cannot decode the token'); 45 | } 46 | 47 | return JSON.parse(decoded); 48 | } 49 | 50 | public getTokenExpirationDate(token: string): Date { 51 | let decoded: any; 52 | decoded = this.decodeToken(token); 53 | 54 | if (!decoded.hasOwnProperty('exp')) { 55 | return null; 56 | } 57 | 58 | const date = new Date(0); // The 0 here is the key, which sets the date to the epoch 59 | date.setUTCSeconds(decoded.exp); 60 | 61 | return date; 62 | } 63 | 64 | public isTokenExpired(token: string, offsetSeconds?: number): boolean { 65 | const date = this.getTokenExpirationDate(token); 66 | offsetSeconds = offsetSeconds || 0; 67 | 68 | if (date == null) { 69 | return false; 70 | } 71 | 72 | // Token expired? 73 | return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Authorization/Policies.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace DatingApp.Authorization 13 | { 14 | public class Policies 15 | { 16 | ///Policy to allow viewing all user records. 17 | public const string ViewAllUsersPolicy = "View All Users"; 18 | 19 | ///Policy to allow adding, removing and updating all user records. 20 | public const string ManageAllUsersPolicy = "Manage All Users"; 21 | 22 | /// Policy to allow viewing details of all roles. 23 | public const string ViewAllRolesPolicy = "View All Roles"; 24 | 25 | /// Policy to allow viewing details of all or specific roles (Requires roleName as parameter). 26 | public const string ViewRoleByRoleNamePolicy = "View Role by RoleName"; 27 | 28 | /// Policy to allow adding, removing and updating all roles. 29 | public const string ManageAllRolesPolicy = "Manage All Roles"; 30 | 31 | /// Policy to allow assigning roles the user has access to (Requires new and current roles as parameter). 32 | public const string AssignAllowedRolesPolicy = "Assign Allowed Roles"; 33 | } 34 | 35 | 36 | 37 | /// 38 | /// Operation Policy to allow adding, viewing, updating and deleting general or specific user records. 39 | /// 40 | public static class AccountManagementOperations 41 | { 42 | public const string CreateOperationName = "Create"; 43 | public const string ReadOperationName = "Read"; 44 | public const string UpdateOperationName = "Update"; 45 | public const string DeleteOperationName = "Delete"; 46 | 47 | public static UserAccountAuthorizationRequirement Create = new UserAccountAuthorizationRequirement(CreateOperationName); 48 | public static UserAccountAuthorizationRequirement Read = new UserAccountAuthorizationRequirement(ReadOperationName); 49 | public static UserAccountAuthorizationRequirement Update = new UserAccountAuthorizationRequirement(UpdateOperationName); 50 | public static UserAccountAuthorizationRequirement Delete = new UserAccountAuthorizationRequirement(DeleteOperationName); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DatingApp/DAL/Core/Interfaces/IAccountManager.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DAL.Core.Interfaces 14 | { 15 | public interface IAccountManager 16 | { 17 | 18 | Task CheckPasswordAsync(ApplicationUser user, string password); 19 | Task<(bool Succeeded, string[] Errors)> CreateRoleAsync(ApplicationRole role, IEnumerable claims); 20 | Task<(bool Succeeded, string[] Errors)> CreateUserAsync(ApplicationUser user, IEnumerable roles, string password); 21 | Task<(bool Succeeded, string[] Errors)> DeleteRoleAsync(ApplicationRole role); 22 | Task<(bool Succeeded, string[] Errors)> DeleteRoleAsync(string roleName); 23 | Task<(bool Succeeded, string[] Errors)> DeleteUserAsync(ApplicationUser user); 24 | Task<(bool Succeeded, string[] Errors)> DeleteUserAsync(string userId); 25 | Task GetRoleByIdAsync(string roleId); 26 | Task GetRoleByNameAsync(string roleName); 27 | Task GetRoleLoadRelatedAsync(string roleName); 28 | Task> GetRolesLoadRelatedAsync(int page, int pageSize); 29 | Task<(ApplicationUser User, string[] Roles)?> GetUserAndRolesAsync(string userId); 30 | Task GetUserByEmailAsync(string email); 31 | Task GetUserByIdAsync(string userId); 32 | Task GetUserByUserNameAsync(string userName); 33 | Task> GetUserRolesAsync(ApplicationUser user); 34 | Task> GetUsersAndRolesAsync(int page, int pageSize); 35 | Task<(bool Succeeded, string[] Errors)> ResetPasswordAsync(ApplicationUser user, string newPassword); 36 | Task TestCanDeleteRoleAsync(string roleId); 37 | Task TestCanDeleteUserAsync(string userId); 38 | Task<(bool Succeeded, string[] Errors)> UpdatePasswordAsync(ApplicationUser user, string currentPassword, string newPassword); 39 | Task<(bool Succeeded, string[] Errors)> UpdateRoleAsync(ApplicationRole role, IEnumerable claims); 40 | Task<(bool Succeeded, string[] Errors)> UpdateUserAsync(ApplicationUser user); 41 | Task<(bool Succeeded, string[] Errors)> UpdateUserAsync(ApplicationUser user, IEnumerable roles); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DatingApp/DAL/Models/ApplicationRole.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Models.Interfaces; 7 | using Microsoft.AspNetCore.Identity; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace DAL.Models 14 | { 15 | public class ApplicationRole : IdentityRole, IAuditableEntity 16 | { 17 | /// 18 | /// Initializes a new instance of . 19 | /// 20 | /// 21 | /// The Id property is initialized to from a new GUID string value. 22 | /// 23 | public ApplicationRole() 24 | { 25 | 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of . 30 | /// 31 | /// The role name. 32 | /// 33 | /// The Id property is initialized to from a new GUID string value. 34 | /// 35 | public ApplicationRole(string roleName) : base(roleName) 36 | { 37 | 38 | } 39 | 40 | 41 | /// 42 | /// Initializes a new instance of . 43 | /// 44 | /// The role name. 45 | /// Description of the role. 46 | /// 47 | /// The Id property is initialized to from a new GUID string value. 48 | /// 49 | public ApplicationRole(string roleName, string description) : base(roleName) 50 | { 51 | Description = description; 52 | } 53 | 54 | 55 | 56 | 57 | 58 | /// 59 | /// Gets or sets the description for this role. 60 | /// 61 | public string Description { get; set; } 62 | public string CreatedBy { get; set; } 63 | public string UpdatedBy { get; set; } 64 | public DateTime CreatedDate { get; set; } 65 | public DateTime UpdatedDate { get; set; } 66 | 67 | /// 68 | /// Navigation property for the users in this role. 69 | /// 70 | public virtual ICollection> Users { get; set; } 71 | 72 | /// 73 | /// Navigation property for claims in this role. 74 | /// 75 | public virtual ICollection> Claims { get; set; } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 |
8 |
9 |
10 |

11 | {{'home.NoWidgets1' | translate}} {{'settings.tab.Preferences' | translate}} 12 | {{'home.NoWidgets2' | translate}} 13 |

14 |
15 |
16 | 17 |
18 | 21 |
22 |
23 |
{{'home.StatisticsTitle' | translate}}
24 | 25 |
26 |
27 | 28 |
29 | 32 |
33 | 34 |
35 | 36 | 43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Authorization/AssignRolesAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Core; 7 | using Microsoft.AspNetCore.Authorization; 8 | using System.Linq; 9 | using System.Security.Claims; 10 | using System.Threading.Tasks; 11 | 12 | namespace DatingApp.Authorization 13 | { 14 | public class AssignRolesAuthorizationRequirement : IAuthorizationRequirement 15 | { 16 | 17 | } 18 | 19 | 20 | 21 | public class AssignRolesAuthorizationHandler : AuthorizationHandler 22 | { 23 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AssignRolesAuthorizationRequirement requirement, (string[] newRoles, string[] currentRoles) roles) 24 | { 25 | if (!GetIsRolesChanged(roles.newRoles, roles.currentRoles)) 26 | { 27 | context.Succeed(requirement); 28 | } 29 | else if (context.User.HasClaim(ClaimConstants.Permission, ApplicationPermissions.AssignRoles)) 30 | { 31 | if (context.User.HasClaim(ClaimConstants.Permission, ApplicationPermissions.ViewRoles)) // If user has ViewRoles permission, then he can assign any roles 32 | context.Succeed(requirement); 33 | 34 | else if (GetIsUserInAllAddedRoles(context.User, roles.newRoles, roles.currentRoles)) // Else user can only assign roles they're part of 35 | context.Succeed(requirement); 36 | } 37 | 38 | 39 | return Task.CompletedTask; 40 | } 41 | 42 | 43 | private bool GetIsRolesChanged(string[] newRoles, string[] currentRoles) 44 | { 45 | if (newRoles == null) 46 | newRoles = new string[] { }; 47 | 48 | if (currentRoles == null) 49 | currentRoles = new string[] { }; 50 | 51 | 52 | bool roleAdded = newRoles.Except(currentRoles).Any(); 53 | bool roleRemoved = currentRoles.Except(newRoles).Any(); 54 | 55 | return roleAdded || roleRemoved; 56 | } 57 | 58 | 59 | private bool GetIsUserInAllAddedRoles(ClaimsPrincipal contextUser, string[] newRoles, string[] currentRoles) 60 | { 61 | if (newRoles == null) 62 | newRoles = new string[] { }; 63 | 64 | if (currentRoles == null) 65 | currentRoles = new string[] { }; 66 | 67 | var addedRoles = newRoles.Except(currentRoles); 68 | 69 | return addedRoles.All(role => contextUser.IsInRole(role)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /DatingApp/DatingApp/ViewModels/UserViewModels.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using FluentValidation; 7 | using DatingApp.Helpers; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.ComponentModel.DataAnnotations; 11 | using System.Linq; 12 | 13 | 14 | namespace DatingApp.ViewModels 15 | { 16 | public class UserViewModel : UserBaseViewModel 17 | { 18 | public bool IsLockedOut { get; set; } 19 | 20 | [MinimumCount(1, ErrorMessage = "Roles cannot be empty")] 21 | public string[] Roles { get; set; } 22 | } 23 | 24 | 25 | 26 | public class UserEditViewModel : UserBaseViewModel 27 | { 28 | public string CurrentPassword { get; set; } 29 | 30 | [MinLength(6, ErrorMessage = "New Password must be at least 6 characters")] 31 | public string NewPassword { get; set; } 32 | 33 | [MinimumCount(1, ErrorMessage = "Roles cannot be empty")] 34 | public string[] Roles { get; set; } 35 | } 36 | 37 | 38 | 39 | public class UserPatchViewModel 40 | { 41 | public string FullName { get; set; } 42 | 43 | public string JobTitle { get; set; } 44 | 45 | public string PhoneNumber { get; set; } 46 | 47 | public string Configuration { get; set; } 48 | } 49 | 50 | 51 | 52 | public abstract class UserBaseViewModel 53 | { 54 | public string Id { get; set; } 55 | 56 | [Required(ErrorMessage = "Username is required"), StringLength(200, MinimumLength = 2, ErrorMessage = "Username must be between 2 and 200 characters")] 57 | public string UserName { get; set; } 58 | 59 | public string FullName { get; set; } 60 | 61 | [Required(ErrorMessage = "Email is required"), StringLength(200, ErrorMessage = "Email must be at most 200 characters"), EmailAddress(ErrorMessage = "Invalid email address")] 62 | public string Email { get; set; } 63 | 64 | public string JobTitle { get; set; } 65 | 66 | public string PhoneNumber { get; set; } 67 | 68 | public string Configuration { get; set; } 69 | 70 | public bool IsEnabled { get; set; } 71 | } 72 | 73 | 74 | 75 | 76 | //public class UserViewModelValidator : AbstractValidator 77 | //{ 78 | // public UserViewModelValidator() 79 | // { 80 | // //Validation logic here 81 | // RuleFor(user => user.UserName).NotEmpty().WithMessage("Username cannot be empty"); 82 | // RuleFor(user => user.Email).EmailAddress().NotEmpty(); 83 | // RuleFor(user => user.Password).NotEmpty().WithMessage("Password cannot be empty").Length(4, 20); 84 | // } 85 | //} 86 | } 87 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickapp", 3 | "version": "2.7.2", 4 | "description": "ASP.NET Core 2.2/Angular 7 startup project template with complete login, user and role management. Plus other useful services for Quick Application Development", 5 | "author": { 6 | "name": "Ebenezer Monney", 7 | "email": "info@ebenmonney.com", 8 | "url": "https://www.ebenmonney.com/about" 9 | }, 10 | "license": "MIT", 11 | "homepage": "https://www.ebenmonney.com/quickapp", 12 | "repository": "https://github.com/emonney/quickapp.git", 13 | "scripts": { 14 | "ng": "ng", 15 | "start": "run-p theme \"serve -- {@}\" --", 16 | "serve": "ng serve", 17 | "theme": "node-sass --watch src/app/assets/themes/ --output src/assets/themes/ --output-style=compressed", 18 | "build": "ng build", 19 | "test": "ng test", 20 | "lint": "ng lint", 21 | "e2e": "ng e2e" 22 | }, 23 | "private": true, 24 | "dependencies": { 25 | "@angular/animations": "~7.2.0", 26 | "@angular/common": "~7.2.0", 27 | "@angular/compiler": "~7.2.0", 28 | "@angular/core": "~7.2.0", 29 | "@angular/forms": "~7.2.0", 30 | "@angular/platform-browser": "~7.2.0", 31 | "@angular/platform-browser-dynamic": "~7.2.0", 32 | "@angular/router": "~7.2.0", 33 | "@ngx-translate/core": "^11.0.1", 34 | "@swimlane/ngx-datatable": "^14.0.0", 35 | "bootstrap": "^4.3.1", 36 | "bootstrap-datepicker": "^1.8.0", 37 | "bootstrap-select": "^1.13.8", 38 | "bootstrap-toggle": "^2.2.2", 39 | "bootswatch": "^4.3.1", 40 | "chart.js": "^2.8.0", 41 | "core-js": "^2.5.4", 42 | "font-awesome": "^4.7.0", 43 | "jquery": "^3.3.1", 44 | "ng2-charts": "^2.0.3", 45 | "ngx-bootstrap": "^3.2.0", 46 | "ngx-toasta": "^0.1.0", 47 | "popper.js": "^1.14.7", 48 | "rxjs": "~6.3.3", 49 | "tslib": "^1.9.0", 50 | "zone.js": "~0.8.26" 51 | }, 52 | "devDependencies": { 53 | "@angular-devkit/build-angular": "~0.13.0", 54 | "@angular/cli": "~7.3.6", 55 | "@angular/compiler-cli": "~7.2.0", 56 | "@angular/language-service": "~7.2.0", 57 | "@types/jasmine": "~2.8.8", 58 | "@types/jasminewd2": "~2.0.3", 59 | "@types/jquery": "^3.3.29", 60 | "@types/node": "~8.9.4", 61 | "codelyzer": "~4.5.0", 62 | "jasmine-core": "~2.99.1", 63 | "jasmine-spec-reporter": "~4.2.1", 64 | "karma": "~4.0.0", 65 | "karma-chrome-launcher": "~2.2.0", 66 | "karma-coverage-istanbul-reporter": "~2.0.1", 67 | "karma-jasmine": "~1.1.2", 68 | "karma-jasmine-html-reporter": "^0.2.2", 69 | "npm-run-all": "^4.1.5", 70 | "protractor": "~5.4.0", 71 | "ts-node": "~7.0.0", 72 | "tslint": "~5.11.0", 73 | "typescript": "~3.2.2" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { NgModule } from '@angular/core'; 7 | import { Routes, RouterModule, DefaultUrlSerializer, UrlSerializer, UrlTree } from '@angular/router'; 8 | 9 | import { LoginComponent } from './components/login/login.component'; 10 | import { HomeComponent } from './components/home/home.component'; 11 | import { CustomersComponent } from './components/customers/customers.component'; 12 | import { ProductsComponent } from './components/products/products.component'; 13 | import { OrdersComponent } from './components/orders/orders.component'; 14 | import { SettingsComponent } from './components/settings/settings.component'; 15 | import { AboutComponent } from './components/about/about.component'; 16 | import { NotFoundComponent } from './components/not-found/not-found.component'; 17 | import { AuthService } from './services/auth.service'; 18 | import { AuthGuard } from './services/auth-guard.service'; 19 | import { Utilities } from './services/utilities'; 20 | 21 | 22 | 23 | export class LowerCaseUrlSerializer extends DefaultUrlSerializer { 24 | parse(url: string): UrlTree { 25 | const possibleSeparators = /[?;#]/; 26 | const indexOfSeparator = url.search(possibleSeparators); 27 | let processedUrl: string; 28 | 29 | if (indexOfSeparator > -1) { 30 | const separator = url.charAt(indexOfSeparator); 31 | const urlParts = Utilities.splitInTwo(url, separator); 32 | urlParts.firstPart = urlParts.firstPart.toLowerCase(); 33 | 34 | processedUrl = urlParts.firstPart + separator + urlParts.secondPart; 35 | } else { 36 | processedUrl = url.toLowerCase(); 37 | } 38 | 39 | return super.parse(processedUrl); 40 | } 41 | } 42 | 43 | 44 | const routes: Routes = [ 45 | { path: '', component: HomeComponent, canActivate: [AuthGuard], data: { title: 'Home' } }, 46 | { path: 'login', component: LoginComponent, data: { title: 'Login' } }, 47 | { path: 'customers', component: CustomersComponent, canActivate: [AuthGuard], data: { title: 'Customers' } }, 48 | { path: 'products', component: ProductsComponent, canActivate: [AuthGuard], data: { title: 'Products' } }, 49 | { path: 'orders', component: OrdersComponent, canActivate: [AuthGuard], data: { title: 'Orders' } }, 50 | { path: 'settings', component: SettingsComponent, canActivate: [AuthGuard], data: { title: 'Settings' } }, 51 | { path: 'about', component: AboutComponent, data: { title: 'About Us' } }, 52 | { path: 'home', redirectTo: '/', pathMatch: 'full' }, 53 | { path: '**', component: NotFoundComponent, data: { title: 'Page Not Found' } } 54 | ]; 55 | 56 | 57 | @NgModule({ 58 | imports: [RouterModule.forRoot(routes)], 59 | exports: [RouterModule], 60 | providers: [ 61 | AuthService, 62 | AuthGuard, 63 | { provide: UrlSerializer, useClass: LowerCaseUrlSerializer }] 64 | }) 65 | export class AppRoutingModule { } 66 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Controllers/CustomerController.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Mvc; 11 | using DAL; 12 | using DatingApp.ViewModels; 13 | using AutoMapper; 14 | using Microsoft.Extensions.Logging; 15 | using DatingApp.Helpers; 16 | 17 | namespace DatingApp.Controllers 18 | { 19 | [Route("api/[controller]")] 20 | public class CustomerController : Controller 21 | { 22 | private IUnitOfWork _unitOfWork; 23 | readonly ILogger _logger; 24 | readonly IEmailSender _emailSender; 25 | 26 | 27 | public CustomerController(IUnitOfWork unitOfWork, ILogger logger, IEmailSender emailSender) 28 | { 29 | _unitOfWork = unitOfWork; 30 | _logger = logger; 31 | _emailSender = emailSender; 32 | } 33 | 34 | 35 | 36 | // GET: api/values 37 | [HttpGet] 38 | public IActionResult Get() 39 | { 40 | var allCustomers = _unitOfWork.Customers.GetAllCustomersData(); 41 | return Ok(Mapper.Map>(allCustomers)); 42 | } 43 | 44 | 45 | 46 | [HttpGet("throw")] 47 | public IEnumerable Throw() 48 | { 49 | throw new InvalidOperationException("This is a test exception: " + DateTime.Now); 50 | } 51 | 52 | 53 | 54 | [HttpGet("email")] 55 | public async Task Email() 56 | { 57 | string recepientName = "QickApp Tester"; // <===== Put the recepient's name here 58 | string recepientEmail = "test@ebenmonney.com"; // <===== Put the recepient's email here 59 | 60 | string message = EmailTemplates.GetTestEmail(recepientName, DateTime.UtcNow); 61 | 62 | (bool success, string errorMsg) = await _emailSender.SendEmailAsync(recepientName, recepientEmail, "Test Email from DatingApp", message); 63 | 64 | if (success) 65 | return "Success"; 66 | 67 | return "Error: " + errorMsg; 68 | } 69 | 70 | 71 | 72 | // GET api/values/5 73 | [HttpGet("{id}")] 74 | public string Get(int id) 75 | { 76 | return "value: " + id; 77 | } 78 | 79 | 80 | 81 | // POST api/values 82 | [HttpPost] 83 | public void Post([FromBody]string value) 84 | { 85 | } 86 | 87 | 88 | 89 | // PUT api/values/5 90 | [HttpPut("{id}")] 91 | public void Put(int id, [FromBody]string value) 92 | { 93 | } 94 | 95 | 96 | 97 | // DELETE api/values/5 98 | [HttpDelete("{id}")] 99 | public void Delete(int id) 100 | { 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/alertify.default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Look and Feel 3 | */ 4 | .alertify, 5 | .alertify-log { 6 | font-family: sans-serif; 7 | } 8 | .alertify { 9 | background: #FFF; 10 | border: 10px solid #333; /* browsers that don't support rgba */ 11 | border: 10px solid rgba(0,0,0,.7); 12 | border-radius: 8px; 13 | box-shadow: 0 3px 3px rgba(0,0,0,.3); 14 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 15 | -moz-background-clip: padding; /* Firefox 3.6 */ 16 | background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */ 17 | } 18 | .alertify-text { 19 | border: 1px solid #CCC; 20 | padding: 10px; 21 | border-radius: 4px; 22 | } 23 | .alertify-button { 24 | border-radius: 4px; 25 | color: #FFF; 26 | font-weight: bold; 27 | padding: 6px 15px; 28 | text-decoration: none; 29 | text-shadow: 1px 1px 0 rgba(0,0,0,.5); 30 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5); 31 | background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0)); 32 | background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0)); 33 | background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0)); 34 | background-image: -o-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0)); 35 | background-image: linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0)); 36 | } 37 | .alertify-button:hover, 38 | .alertify-button:focus { 39 | outline: none; 40 | background-image: -webkit-linear-gradient(to bottom, rgba(0,0,0,.1), rgba(0,0,0,0)); 41 | background-image: -moz-linear-gradient(to bottom, rgba(0,0,0,.1), rgba(0,0,0,0)); 42 | background-image: -ms-linear-gradient(to bottom, rgba(0,0,0,.1), rgba(0,0,0,0)); 43 | background-image: -o-linear-gradient(to bottom, rgba(0,0,0,.1), rgba(0,0,0,0)); 44 | background-image: linear-gradient(to bottom, rgba(0,0,0,.1), rgba(0,0,0,0)); 45 | } 46 | .alertify-button:focus { 47 | box-shadow: 0 0 15px #2B72D5; 48 | } 49 | .alertify-button:active { 50 | position: relative; 51 | box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); 52 | } 53 | .alertify-button-cancel, 54 | .alertify-button-cancel:hover, 55 | .alertify-button-cancel:focus { 56 | background-color: #FE1A00; 57 | border: 1px solid #D83526; 58 | } 59 | .alertify-button-ok, 60 | .alertify-button-ok:hover, 61 | .alertify-button-ok:focus { 62 | background-color: #5CB811; 63 | border: 1px solid #3B7808; 64 | } 65 | 66 | .alertify-log { 67 | background: #1F1F1F; 68 | background: rgba(0,0,0,.9); 69 | padding: 15px; 70 | border-radius: 4px; 71 | color: #FFF; 72 | text-shadow: -1px -1px 0 rgba(0,0,0,.5); 73 | } 74 | .alertify-log-error { 75 | background: #FE1A00; 76 | background: rgba(254,26,0,.9); 77 | } 78 | .alertify-log-success { 79 | background: #5CB811; 80 | background: rgba(92,184,17,.9); 81 | } 82 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/about/about.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 |

Welcome to Dating Application

8 |

This Application build used by following technologies

9 | 10 |
Technologies
11 | 20 | 21 |
22 |
Features
23 |
    24 |
  • A complete backend and frontend project structure to build on, with login, user and permission-based role management integrated
  • 25 |
  • Clean and simple Angular/ASP.NET Core code to serve as a guide
  • 26 |
  • Data Access Layer built with the Repository and Unit of Work Pattern
  • 27 |
  • Code First Database
  • 28 |
  • A RESTful API Design
  • 29 |
  • Dialog and Notification Services
  • 30 |
  • Configuration Page and Configuration Service
  • 31 |
  • Integrated Internationaliztion
  • 32 |
  • Theming with SASS
  • 33 |
  • Ready-to-use email API
  • 34 |
  • Handling Access and Refresh Tokens with WebStorage (Bearer authentication) - No Cookies
  • 35 |
  • CRUD APIs
  • 36 |
  • etc.
  • 37 |
38 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/statistics-demo.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
{{label}}
{{d && d.label.split(' ').pop()}}{{d && d.data[j]}}
31 |
32 | 33 |
34 | 35 | 56 |
57 | 58 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/Authorization/UserAccountAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using DAL.Core; 7 | using Microsoft.AspNetCore.Authorization; 8 | using DatingApp.Helpers; 9 | using System.Collections.Generic; 10 | using System.Security.Claims; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace DatingApp.Authorization 15 | { 16 | public class UserAccountAuthorizationRequirement : IAuthorizationRequirement 17 | { 18 | public UserAccountAuthorizationRequirement(string operationName) 19 | { 20 | this.OperationName = operationName; 21 | } 22 | 23 | 24 | public string OperationName { get; private set; } 25 | } 26 | 27 | 28 | 29 | public class ViewUserAuthorizationHandler : AuthorizationHandler 30 | { 31 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserAccountAuthorizationRequirement requirement, string targetUserId) 32 | { 33 | if (context.User == null || requirement.OperationName != AccountManagementOperations.ReadOperationName) 34 | return Task.CompletedTask; 35 | 36 | if (context.User.HasClaim(ClaimConstants.Permission, ApplicationPermissions.ViewUsers) || GetIsSameUser(context.User, targetUserId)) 37 | context.Succeed(requirement); 38 | 39 | return Task.CompletedTask; 40 | } 41 | 42 | 43 | private bool GetIsSameUser(ClaimsPrincipal user, string targetUserId) 44 | { 45 | if (string.IsNullOrWhiteSpace(targetUserId)) 46 | return false; 47 | 48 | return Utilities.GetUserId(user) == targetUserId; 49 | } 50 | } 51 | 52 | 53 | 54 | public class ManageUserAuthorizationHandler : AuthorizationHandler 55 | { 56 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserAccountAuthorizationRequirement requirement, string targetUserId) 57 | { 58 | if (context.User == null || 59 | (requirement.OperationName != AccountManagementOperations.CreateOperationName && 60 | requirement.OperationName != AccountManagementOperations.UpdateOperationName && 61 | requirement.OperationName != AccountManagementOperations.DeleteOperationName)) 62 | return Task.CompletedTask; 63 | 64 | if (context.User.HasClaim(ClaimConstants.Permission, ApplicationPermissions.ManageUsers) || GetIsSameUser(context.User, targetUserId)) 65 | context.Succeed(requirement); 66 | 67 | return Task.CompletedTask; 68 | } 69 | 70 | 71 | private bool GetIsSameUser(ClaimsPrincipal user, string targetUserId) 72 | { 73 | if (string.IsNullOrWhiteSpace(targetUserId)) 74 | return false; 75 | 76 | return Utilities.GetUserId(user) == targetUserId; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; 7 | import { Router, NavigationExtras, ActivatedRoute } from '@angular/router'; 8 | 9 | 10 | import { fadeInOut } from '../../services/animations'; 11 | import { BootstrapTabDirective } from '../../directives/bootstrap-tab.directive'; 12 | import { AccountService } from '../../services/account.service'; 13 | import { Permission } from '../../models/permission.model'; 14 | 15 | 16 | @Component({ 17 | selector: 'settings', 18 | templateUrl: './settings.component.html', 19 | styleUrls: ['./settings.component.scss'], 20 | animations: [fadeInOut] 21 | }) 22 | export class SettingsComponent implements OnInit, OnDestroy { 23 | 24 | isProfileActivated = true; 25 | isPreferencesActivated = false; 26 | isUsersActivated = false; 27 | isRolesActivated = false; 28 | 29 | fragmentSubscription: any; 30 | 31 | readonly profileTab = 'profile'; 32 | readonly preferencesTab = 'preferences'; 33 | readonly usersTab = 'users'; 34 | readonly rolesTab = 'roles'; 35 | 36 | 37 | @ViewChild('tab') 38 | tab: BootstrapTabDirective; 39 | 40 | 41 | constructor(private router: Router, private route: ActivatedRoute, private accountService: AccountService) { 42 | } 43 | 44 | 45 | ngOnInit() { 46 | this.fragmentSubscription = this.route.fragment.subscribe(anchor => this.showContent(anchor)); 47 | } 48 | 49 | 50 | ngOnDestroy() { 51 | this.fragmentSubscription.unsubscribe(); 52 | } 53 | 54 | showContent(anchor: string) { 55 | if (anchor) { 56 | anchor = anchor.toLowerCase(); 57 | } 58 | 59 | if ((this.isFragmentEquals(anchor, this.usersTab) && !this.canViewUsers) || 60 | (this.isFragmentEquals(anchor, this.rolesTab) && !this.canViewRoles)) { 61 | return; 62 | } 63 | 64 | this.tab.show(`#${anchor || this.profileTab}Tab`); 65 | } 66 | 67 | 68 | isFragmentEquals(fragment1: string, fragment2: string) { 69 | 70 | if (fragment1 == null) { 71 | fragment1 = ''; 72 | } 73 | 74 | if (fragment2 == null) { 75 | fragment2 = ''; 76 | } 77 | 78 | return fragment1.toLowerCase() == fragment2.toLowerCase(); 79 | } 80 | 81 | 82 | onShowTab(event) { 83 | const activeTab = event.target.hash.split('#', 2).pop(); 84 | 85 | this.isProfileActivated = activeTab == this.profileTab; 86 | this.isPreferencesActivated = activeTab == this.preferencesTab; 87 | this.isUsersActivated = activeTab == this.usersTab; 88 | this.isRolesActivated = activeTab == this.rolesTab; 89 | 90 | this.router.navigate([], { fragment: activeTab }); 91 | } 92 | 93 | 94 | get canViewUsers() { 95 | return this.accountService.userHasPermission(Permission.viewUsersPermission); 96 | } 97 | 98 | get canViewRoles() { 99 | return this.accountService.userHasPermission(Permission.viewRolesPermission); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/services/app-translation.service.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { TranslateService, TranslateLoader } from '@ngx-translate/core'; 8 | import { Observable, Subject, of } from 'rxjs'; 9 | 10 | 11 | 12 | @Injectable() 13 | export class AppTranslationService { 14 | 15 | private onLanguageChanged = new Subject(); 16 | languageChanged$ = this.onLanguageChanged.asObservable(); 17 | 18 | constructor(private translate: TranslateService) { 19 | this.addLanguages(['en', 'fr', 'de', 'pt', 'ar', 'ko']); 20 | this.setDefaultLanguage('en'); 21 | } 22 | 23 | addLanguages(lang: string[]) { 24 | this.translate.addLangs(lang); 25 | } 26 | 27 | setDefaultLanguage(lang: string) { 28 | this.translate.setDefaultLang(lang); 29 | } 30 | 31 | getDefaultLanguage() { 32 | return this.translate.defaultLang; 33 | } 34 | 35 | getBrowserLanguage() { 36 | return this.translate.getBrowserLang(); 37 | } 38 | 39 | getCurrentLanguage() { 40 | return this.translate.currentLang; 41 | } 42 | 43 | getLoadedLanguages() { 44 | return this.translate.langs; 45 | } 46 | 47 | useBrowserLanguage(): string | void { 48 | const browserLang = this.getBrowserLanguage(); 49 | 50 | if (browserLang.match(/en|fr|de|pt|ar|ko/)) { 51 | this.changeLanguage(browserLang); 52 | return browserLang; 53 | } 54 | } 55 | 56 | useDefaultLangage() { 57 | return this.changeLanguage(null); 58 | } 59 | 60 | changeLanguage(language: string) { 61 | if (!language) { 62 | language = this.getDefaultLanguage(); 63 | } 64 | 65 | if (language != this.translate.currentLang) { 66 | setTimeout(() => { 67 | this.translate.use(language); 68 | this.onLanguageChanged.next(language); 69 | }); 70 | } 71 | 72 | return language; 73 | } 74 | 75 | 76 | getTranslation(key: string | Array, interpolateParams?: Object): string | any { 77 | return this.translate.instant(key, interpolateParams); 78 | } 79 | 80 | 81 | getTranslationAsync(key: string | Array, interpolateParams?: Object): Observable { 82 | return this.translate.get(key, interpolateParams); 83 | } 84 | 85 | } 86 | 87 | 88 | export class TranslateLanguageLoader implements TranslateLoader { 89 | 90 | public getTranslation(lang: string): any { 91 | 92 | // Note Dynamic require(variable) will not work. Require is always at compile time 93 | 94 | switch (lang) { 95 | case 'en': 96 | return of(require('../assets/locale/en.json')); 97 | case 'fr': 98 | return of(require('../assets/locale/fr.json')); 99 | case 'de': 100 | return of(require('../assets/locale/de.json')); 101 | case 'pt': 102 | return of(require('../assets/locale/pt.json')); 103 | case 'ar': 104 | return of(require('../assets/locale/ar.json')); 105 | case 'ko': 106 | return of(require('../assets/locale/ko.json')); 107 | default: 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/assets/styles-external.css: -------------------------------------------------------------------------------- 1 | #pre-bootstrap { 2 | background-color: #262626; 3 | bottom: 0px; 4 | left: 0px; 5 | position: fixed; 6 | right: 0px; 7 | top: 0px; 8 | z-index: 999999; 9 | } 10 | 11 | #pre-bootstrap div.messaging { 12 | color: #FFFFFF; 13 | font-family: monospace; 14 | left: 0px; 15 | margin-top: -107px; 16 | position: absolute; 17 | right: 0px; 18 | text-align: center; 19 | top: 50%; 20 | } 21 | 22 | #pre-bootstrap h1 { 23 | font-size: 26px; 24 | line-height: 35px; 25 | margin: 0px 0px 20px 0px; 26 | } 27 | 28 | #pre-bootstrap p { 29 | font-size: 18px; 30 | line-height: 14px; 31 | margin: 0px 0px 0px 0px; 32 | } 33 | 34 | 35 | 36 | .prebootShow { 37 | opacity: 1 !important; 38 | } 39 | 40 | .prebootStep { 41 | opacity: 0; 42 | transition: .5s ease-in-out all; 43 | } 44 | 45 | 46 | /*loader*/ 47 | /* Absolute Center Spinner */ 48 | 49 | 50 | .cs-loader { 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | height: 100%; 55 | width: 100%; 56 | } 57 | 58 | .cs-loader-inner { 59 | transform: translateY(-50%); 60 | top: 60%; 61 | position: absolute; 62 | width: calc(100% - 200px); 63 | color: #FFF; 64 | padding: 0 100px; 65 | text-align: center; 66 | } 67 | 68 | .cs-loader-inner label { 69 | font-size: 20px; 70 | opacity: 0; 71 | display: inline-block; 72 | color: gray; 73 | } 74 | 75 | @keyframes lol { 76 | 0% { 77 | opacity: 0; 78 | transform: translateX(-300px); 79 | } 80 | 81 | 33% { 82 | opacity: 1; 83 | transform: translateX(0px); 84 | } 85 | 86 | 66% { 87 | opacity: 1; 88 | transform: translateX(0px); 89 | } 90 | 91 | 100% { 92 | opacity: 0; 93 | transform: translateX(300px); 94 | } 95 | } 96 | 97 | @-webkit-keyframes lol { 98 | 0% { 99 | opacity: 0; 100 | -webkit-transform: translateX(-300px); 101 | } 102 | 103 | 33% { 104 | opacity: 1; 105 | -webkit-transform: translateX(0px); 106 | } 107 | 108 | 66% { 109 | opacity: 1; 110 | -webkit-transform: translateX(0px); 111 | } 112 | 113 | 100% { 114 | opacity: 0; 115 | -webkit-transform: translateX(300px); 116 | } 117 | } 118 | 119 | .cs-loader-inner label:nth-child(6) { 120 | -webkit-animation: lol 3s infinite ease-in-out; 121 | animation: lol 3s infinite ease-in-out; 122 | } 123 | 124 | .cs-loader-inner label:nth-child(5) { 125 | -webkit-animation: lol 3s 100ms infinite ease-in-out; 126 | animation: lol 3s 100ms infinite ease-in-out; 127 | } 128 | 129 | .cs-loader-inner label:nth-child(4) { 130 | -webkit-animation: lol 3s 200ms infinite ease-in-out; 131 | animation: lol 3s 200ms infinite ease-in-out; 132 | } 133 | 134 | .cs-loader-inner label:nth-child(3) { 135 | -webkit-animation: lol 3s 300ms infinite ease-in-out; 136 | animation: lol 3s 300ms infinite ease-in-out; 137 | } 138 | 139 | .cs-loader-inner label:nth-child(2) { 140 | -webkit-animation: lol 3s 400ms infinite ease-in-out; 141 | animation: lol 3s 400ms infinite ease-in-out; 142 | } 143 | 144 | .cs-loader-inner label:nth-child(1) { 145 | -webkit-animation: lol 3s 500ms infinite ease-in-out; 146 | animation: lol 3s 500ms infinite ease-in-out; 147 | } 148 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/roles-management.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |
8 | 17 |
18 | 19 | 28 | 29 | 30 | 31 | {{value}} 32 | 33 | 34 | 35 | 36 | {{'roles.management.Edit' | translate}} 37 | {{'roles.management.Details' | translate}} 38 | {{canManageRoles ? '|' : ''}} 39 | {{'roles.management.Delete' | translate}} 40 | 41 | 42 | 43 | 59 |
60 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/controls/users-management.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |
8 | 17 |
18 | 19 | 28 | 29 | 30 | 31 | {{value}} 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{value}} 39 | 40 | 41 | 42 | 43 | {{role}} 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 70 |
71 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /** 7 | * This file includes polyfills needed by Angular and is loaded before the app. 8 | * You can add your own extra polyfills to this file. 9 | * 10 | * This file is divided into 2 sections: 11 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 12 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 13 | * file. 14 | * 15 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 16 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 17 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 18 | * 19 | * Learn more in https://angular.io/guide/browser-support 20 | */ 21 | 22 | /*************************************************************************************************** 23 | * BROWSER POLYFILLS 24 | */ 25 | 26 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 27 | // import 'core-js/es6/symbol'; 28 | // import 'core-js/es6/object'; 29 | // import 'core-js/es6/function'; 30 | // import 'core-js/es6/parse-int'; 31 | // import 'core-js/es6/parse-float'; 32 | // import 'core-js/es6/number'; 33 | // import 'core-js/es6/math'; 34 | // import 'core-js/es6/string'; 35 | // import 'core-js/es6/date'; 36 | // import 'core-js/es6/array'; 37 | // import 'core-js/es6/regexp'; 38 | // import 'core-js/es6/map'; 39 | // import 'core-js/es6/weak-map'; 40 | // import 'core-js/es6/set'; 41 | 42 | /** 43 | * If the application will be indexed by Google Search, the following is required. 44 | * Googlebot uses a renderer based on Chrome 41. 45 | * https://developers.google.com/search/docs/guides/rendering 46 | **/ 47 | // import 'core-js/es6/array'; 48 | 49 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 50 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 51 | 52 | /** IE10 and IE11 requires the following for the Reflect API. */ 53 | // import 'core-js/es6/reflect'; 54 | 55 | /** 56 | * Web Animations `@angular/platform-browser/animations` 57 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 58 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 59 | **/ 60 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 61 | 62 | /** 63 | * By default, zone.js will patch all possible macroTask and DomEvents 64 | * user can disable parts of macroTask/DomEvents patch by setting following flags 65 | */ 66 | 67 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 68 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 69 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 70 | 71 | /* 72 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 73 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 74 | */ 75 | // (window as any).__Zone_enable_cross_context_check = true; 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "linebreak-style": [ true, "CRLF" ], 19 | "forin": true, 20 | "import-blacklist": [ 21 | true, 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": [ 26 | true, 27 | "spaces" 28 | ], 29 | "interface-over-type-literal": true, 30 | "label-position": true, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-access": false, 36 | "member-ordering": [ 37 | true, 38 | { 39 | "order": [ 40 | "static-field", 41 | "instance-field", 42 | "static-method", 43 | "instance-method" 44 | ] 45 | } 46 | ], 47 | "no-arg": true, 48 | "no-bitwise": true, 49 | "no-console": [ 50 | true, 51 | "debug", 52 | "info", 53 | "time", 54 | "timeEnd", 55 | "trace" 56 | ], 57 | "no-construct": true, 58 | "no-debugger": true, 59 | "no-duplicate-super": true, 60 | "no-empty": false, 61 | "no-empty-interface": true, 62 | "no-eval": true, 63 | "no-inferrable-types": [ 64 | true, 65 | "ignore-params" 66 | ], 67 | "no-misused-new": true, 68 | "no-non-null-assertion": true, 69 | "no-redundant-jsdoc": true, 70 | "no-shadowed-variable": true, 71 | "no-string-literal": false, 72 | "no-string-throw": true, 73 | "no-switch-case-fall-through": true, 74 | "no-trailing-whitespace": true, 75 | "no-unnecessary-initializer": true, 76 | "no-unused-expression": true, 77 | "no-use-before-declare": true, 78 | "no-var-keyword": true, 79 | "object-literal-sort-keys": false, 80 | "one-line": [ 81 | true, 82 | "check-open-brace", 83 | "check-catch", 84 | "check-else", 85 | "check-whitespace" 86 | ], 87 | "prefer-const": true, 88 | "quotemark": [ 89 | true, 90 | "single" 91 | ], 92 | "radix": true, 93 | "semicolon": [ 94 | true, 95 | "always" 96 | ], 97 | "triple-equals": [ 98 | true, 99 | "allow-null-check" 100 | ], 101 | "typedef-whitespace": [ 102 | true, 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | } 110 | ], 111 | "unified-signatures": true, 112 | "variable-name": false, 113 | "whitespace": [ 114 | true, 115 | "check-branch", 116 | "check-decl", 117 | "check-operator", 118 | "check-separator", 119 | "check-type" 120 | ], 121 | "no-output-on-prefix": true, 122 | "use-input-property-decorator": true, 123 | "use-output-property-decorator": true, 124 | "use-host-property-decorator": true, 125 | "no-input-rename": true, 126 | "no-output-rename": true, 127 | "use-life-cycle-interface": true, 128 | "use-pipe-transform-interface": true, 129 | "component-class-suffix": true, 130 | "directive-class-suffix": true 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/directives/bootstrap-datepicker.directive.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { Directive, ElementRef, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; 7 | import { Observable, Subscription, fromEvent } from 'rxjs'; 8 | 9 | 10 | declare var $: any; 11 | 12 | @Directive({ 13 | selector: '[bootstrapDatepicker]', 14 | exportAs: 'bootstrap-datepicker' 15 | }) 16 | export class BootstrapDatepickerDirective implements OnInit, OnDestroy { 17 | 18 | private _isShown = false; 19 | private updateTimeout; 20 | private changedSubscription: Subscription; 21 | private shownSubscription: Subscription; 22 | private hiddenSubscription: Subscription; 23 | 24 | get isShown() { 25 | return this._isShown; 26 | } 27 | 28 | @Input() 29 | options = {}; 30 | 31 | @Input() 32 | set ngModel(value) { 33 | this.tryUpdate(value); 34 | } 35 | 36 | 37 | @Output() 38 | ngModelChange = new EventEmitter(); 39 | 40 | 41 | constructor(private el: ElementRef) { 42 | this.changedSubscription = fromEvent($(this.el.nativeElement), 'change').subscribe((e: any) => setTimeout(() => this.ngModelChange.emit(e.target.value))); 43 | this.shownSubscription = fromEvent($(this.el.nativeElement), 'show').subscribe((e: any) => this._isShown = true); 44 | this.hiddenSubscription = fromEvent($(this.el.nativeElement), 'hide').subscribe((e: any) => this._isShown = false); 45 | } 46 | 47 | 48 | 49 | ngOnInit() { 50 | this.initialize(this.options); 51 | } 52 | 53 | ngOnDestroy() { 54 | this.destroy(); 55 | } 56 | 57 | 58 | 59 | 60 | initialize(options?: any) { 61 | $(this.el.nativeElement).datepicker(options); 62 | } 63 | 64 | destroy() { 65 | if (this.changedSubscription) { 66 | this.changedSubscription.unsubscribe(); 67 | this.shownSubscription.unsubscribe(); 68 | this.hiddenSubscription.unsubscribe(); 69 | } 70 | 71 | $(this.el.nativeElement).datepicker('destroy'); 72 | } 73 | 74 | 75 | 76 | show() { 77 | $(this.el.nativeElement).datepicker('show'); 78 | } 79 | 80 | 81 | hide() { 82 | $(this.el.nativeElement).datepicker('hide'); 83 | } 84 | 85 | 86 | toggle() { 87 | this.isShown ? this.hide() : this.show(); 88 | } 89 | 90 | 91 | private tryUpdate(value) { 92 | 93 | clearTimeout(this.updateTimeout); 94 | 95 | if (!$(this.el.nativeElement).is(':focus')) { 96 | this.update(value); 97 | } else { 98 | this.updateTimeout = setTimeout(() => { 99 | this.updateTimeout = null; 100 | this.tryUpdate(value); 101 | }, 100); 102 | } 103 | } 104 | 105 | update(value) { 106 | setTimeout(() => $(this.el.nativeElement).datepicker('update', value)); 107 | } 108 | 109 | 110 | setDate(value) { 111 | setTimeout(() => $(this.el.nativeElement).datepicker('setDate', value)); 112 | } 113 | 114 | 115 | setUTCDate(value) { 116 | setTimeout(() => $(this.el.nativeElement).datepicker('setUTCDate', value)); 117 | } 118 | 119 | 120 | clearDates() { 121 | setTimeout(() => $(this.el.nativeElement).datepicker('clearDates')); 122 | } 123 | 124 | 125 | getDate() { 126 | $(this.el.nativeElement).datepicker('getDate'); 127 | } 128 | 129 | 130 | getUTCDate() { 131 | $(this.el.nativeElement).datepicker('getUTCDate'); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 58 |
59 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | import { TestBed, async } from '@angular/core/testing'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | import { FormsModule } from '@angular/forms'; 9 | import { HttpClientModule } from '@angular/common/http'; 10 | 11 | import { AppComponent } from '../components/app.component'; 12 | import { LoginComponent } from '../components/login/login.component'; 13 | import { NotificationsViewerComponent } from '../components/controls/notifications-viewer.component'; 14 | 15 | import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; 16 | import { NgxDatatableModule } from '@swimlane/ngx-datatable'; 17 | import { ToastaModule } from 'ngx-toasta'; 18 | import { ModalModule } from 'ngx-bootstrap/modal'; 19 | import { TooltipModule } from 'ngx-bootstrap/tooltip'; 20 | import { PopoverModule } from 'ngx-bootstrap/popover'; 21 | 22 | import { AuthService } from '../services/auth.service'; 23 | import { AppTitleService } from '../services/app-title.service'; 24 | import { AppTranslationService, TranslateLanguageLoader } from '../services/app-translation.service'; 25 | import { ConfigurationService } from '../services/configuration.service'; 26 | import { ThemeManager } from '../services/theme-manager'; 27 | import { AlertService } from '../services/alert.service'; 28 | import { LocalStoreManager } from '../services/local-store-manager.service'; 29 | import { EndpointFactory } from '../services/endpoint-factory.service'; 30 | import { NotificationService } from '../services/notification.service'; 31 | import { NotificationEndpoint } from '../services/notification-endpoint.service'; 32 | import { AccountService } from '../services/account.service'; 33 | import { AccountEndpoint } from '../services/account-endpoint.service'; 34 | 35 | describe('AppComponent', () => { 36 | beforeEach(async(() => { 37 | TestBed.configureTestingModule({ 38 | imports: [ 39 | HttpClientModule, 40 | FormsModule, 41 | RouterTestingModule, 42 | TranslateModule.forRoot({ 43 | loader: { 44 | provide: TranslateLoader, 45 | useClass: TranslateLanguageLoader 46 | } 47 | }), 48 | NgxDatatableModule, 49 | ToastaModule.forRoot(), 50 | TooltipModule.forRoot(), 51 | PopoverModule.forRoot(), 52 | ModalModule.forRoot() 53 | ], 54 | declarations: [ 55 | AppComponent, 56 | LoginComponent, 57 | NotificationsViewerComponent 58 | ], 59 | providers: [ 60 | AuthService, 61 | AlertService, 62 | ConfigurationService, 63 | ThemeManager, 64 | AppTitleService, 65 | AppTranslationService, 66 | NotificationService, 67 | NotificationEndpoint, 68 | AccountService, 69 | AccountEndpoint, 70 | LocalStoreManager, 71 | EndpointFactory 72 | ] 73 | }).compileComponents(); 74 | })); 75 | 76 | it('should create the app', () => { 77 | const fixture = TestBed.createComponent(AppComponent); 78 | const app = fixture.debugElement.componentInstance; 79 | expect(app).toBeTruthy(); 80 | }); 81 | 82 | it(`should have as title 'DatingApp'`, () => { 83 | const fixture = TestBed.createComponent(AppComponent); 84 | const app = fixture.debugElement.componentInstance; 85 | expect(app.appTitle).toEqual('DatingApp'); 86 | }); 87 | 88 | it('should render Loaded! in a h1 tag', () => { 89 | const fixture = TestBed.createComponent(AppComponent); 90 | fixture.detectChanges(); 91 | const compiled = fixture.debugElement.nativeElement; 92 | expect(compiled.querySelector('h1').textContent).toContain('Loaded!'); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /DatingApp/DAL/Core/ApplicationPermissions.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Collections.ObjectModel; 12 | 13 | namespace DAL.Core 14 | { 15 | public static class ApplicationPermissions 16 | { 17 | public static ReadOnlyCollection AllPermissions; 18 | 19 | 20 | public const string UsersPermissionGroupName = "User Permissions"; 21 | public static ApplicationPermission ViewUsers = new ApplicationPermission("View Users", "users.view", UsersPermissionGroupName, "Permission to view other users account details"); 22 | public static ApplicationPermission ManageUsers = new ApplicationPermission("Manage Users", "users.manage", UsersPermissionGroupName, "Permission to create, delete and modify other users account details"); 23 | 24 | public const string RolesPermissionGroupName = "Role Permissions"; 25 | public static ApplicationPermission ViewRoles = new ApplicationPermission("View Roles", "roles.view", RolesPermissionGroupName, "Permission to view available roles"); 26 | public static ApplicationPermission ManageRoles = new ApplicationPermission("Manage Roles", "roles.manage", RolesPermissionGroupName, "Permission to create, delete and modify roles"); 27 | public static ApplicationPermission AssignRoles = new ApplicationPermission("Assign Roles", "roles.assign", RolesPermissionGroupName, "Permission to assign roles to users"); 28 | 29 | 30 | static ApplicationPermissions() 31 | { 32 | List allPermissions = new List() 33 | { 34 | ViewUsers, 35 | ManageUsers, 36 | 37 | ViewRoles, 38 | ManageRoles, 39 | AssignRoles 40 | }; 41 | 42 | AllPermissions = allPermissions.AsReadOnly(); 43 | } 44 | 45 | public static ApplicationPermission GetPermissionByName(string permissionName) 46 | { 47 | return AllPermissions.Where(p => p.Name == permissionName).SingleOrDefault(); 48 | } 49 | 50 | public static ApplicationPermission GetPermissionByValue(string permissionValue) 51 | { 52 | return AllPermissions.Where(p => p.Value == permissionValue).SingleOrDefault(); 53 | } 54 | 55 | public static string[] GetAllPermissionValues() 56 | { 57 | return AllPermissions.Select(p => p.Value).ToArray(); 58 | } 59 | 60 | public static string[] GetAdministrativePermissionValues() 61 | { 62 | return new string[] { ManageUsers, ManageRoles, AssignRoles }; 63 | } 64 | } 65 | 66 | 67 | 68 | public class ApplicationPermission 69 | { 70 | public ApplicationPermission() 71 | { } 72 | 73 | public ApplicationPermission(string name, string value, string groupName, string description = null) 74 | { 75 | Name = name; 76 | Value = value; 77 | GroupName = groupName; 78 | Description = description; 79 | } 80 | 81 | 82 | 83 | public string Name { get; set; } 84 | public string Value { get; set; } 85 | public string GroupName { get; set; } 86 | public string Description { get; set; } 87 | 88 | 89 | public override string ToString() 90 | { 91 | return Value; 92 | } 93 | 94 | 95 | public static implicit operator string(ApplicationPermission permission) 96 | { 97 | return permission.Value; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/IdentityServerConfig.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using DAL.Core; 9 | using IdentityModel; 10 | using IdentityServer4; 11 | using IdentityServer4.Models; 12 | 13 | namespace DatingApp 14 | { 15 | public class IdentityServerConfig 16 | { 17 | public const string ApiName = "quickapp_api"; 18 | public const string ApiFriendlyName = "DatingApp API"; 19 | public const string QuickAppClientID = "quickapp_spa"; 20 | public const string SwaggerClientID = "swaggerui"; 21 | 22 | // Identity resources (used by UserInfo endpoint). 23 | public static IEnumerable GetIdentityResources() 24 | { 25 | return new List 26 | { 27 | new IdentityResources.OpenId(), 28 | new IdentityResources.Profile(), 29 | new IdentityResources.Phone(), 30 | new IdentityResources.Email(), 31 | new IdentityResource(ScopeConstants.Roles, new List { JwtClaimTypes.Role }) 32 | }; 33 | } 34 | 35 | // Api resources. 36 | public static IEnumerable GetApiResources() 37 | { 38 | return new List 39 | { 40 | new ApiResource(ApiName) { 41 | UserClaims = { 42 | JwtClaimTypes.Name, 43 | JwtClaimTypes.Email, 44 | JwtClaimTypes.PhoneNumber, 45 | JwtClaimTypes.Role, 46 | ClaimConstants.Permission 47 | } 48 | } 49 | }; 50 | } 51 | 52 | // Clients want to access resources. 53 | public static IEnumerable GetClients() 54 | { 55 | // Clients credentials. 56 | return new List 57 | { 58 | // http://docs.identityserver.io/en/release/reference/client.html. 59 | new Client 60 | { 61 | ClientId = IdentityServerConfig.QuickAppClientID, 62 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant. 63 | AllowAccessTokensViaBrowser = true, 64 | RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint. 65 | 66 | AllowedScopes = { 67 | IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint. 68 | IdentityServerConstants.StandardScopes.Profile, 69 | IdentityServerConstants.StandardScopes.Phone, 70 | IdentityServerConstants.StandardScopes.Email, 71 | ScopeConstants.Roles, 72 | ApiName 73 | }, 74 | AllowOfflineAccess = true, // For refresh token. 75 | RefreshTokenExpiration = TokenExpiration.Sliding, 76 | RefreshTokenUsage = TokenUsage.OneTimeOnly, 77 | //AccessTokenLifetime = 900, // Lifetime of access token in seconds. 78 | //AbsoluteRefreshTokenLifetime = 7200, 79 | //SlidingRefreshTokenLifetime = 900, 80 | }, 81 | 82 | new Client 83 | { 84 | ClientId = SwaggerClientID, 85 | ClientName = "Swagger UI", 86 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 87 | AllowAccessTokensViaBrowser = true, 88 | RequireClientSecret = false, 89 | 90 | AllowedScopes = { 91 | ApiName 92 | } 93 | } 94 | }; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/components/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 |
8 | 30 |
31 |
32 |

{{'settings.header.UserProfile' | translate}}

33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 |

{{'settings.header.UserPreferences' | translate}}

41 |
42 |
43 | 44 |
45 |
46 |
47 |

{{'settings.header.UsersManagements' | translate}}

48 |
49 |
50 | 51 |
52 |
53 |
54 |

{{'settings.header.RolesManagement' | translate}}

55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /DatingApp/DatingApp/ClientApp/src/app/assets/styles/alertify.core.css: -------------------------------------------------------------------------------- 1 | .alertify, 2 | .alertify-show, 3 | .alertify-log { 4 | -webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 5 | -moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 6 | -ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 7 | -o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); 8 | transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */ 9 | } 10 | .alertify-hide { 11 | -webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 12 | -moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | -ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 14 | -o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 15 | transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 16 | } 17 | .alertify-log-hide { 18 | -webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 19 | -moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 20 | -ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 21 | -o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); 22 | transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */ 23 | } 24 | .alertify-cover { 25 | position: fixed; z-index: 99999; 26 | top: 0; right: 0; bottom: 0; left: 0; 27 | background-color:white; 28 | filter:alpha(opacity=0); 29 | opacity:0; 30 | } 31 | .alertify-cover-hidden { 32 | display: none; 33 | } 34 | .alertify { 35 | position: fixed; z-index: 99999; 36 | top: 50px; left: 50%; 37 | width: 550px; 38 | margin-left: -275px; 39 | opacity: 1; 40 | } 41 | .alertify-hidden { 42 | -webkit-transform: translate(0,-150px); 43 | -moz-transform: translate(0,-150px); 44 | -ms-transform: translate(0,-150px); 45 | -o-transform: translate(0,-150px); 46 | transform: translate(0,-150px); 47 | opacity: 0; 48 | display: none; 49 | } 50 | /* overwrite display: none; for everything except IE6-8 */ 51 | :root *> .alertify-hidden { 52 | display: block; 53 | visibility: hidden; 54 | } 55 | .alertify-logs { 56 | position: fixed; 57 | z-index: 5000; 58 | bottom: 10px; 59 | right: 10px; 60 | width: 300px; 61 | } 62 | .alertify-logs-hidden { 63 | display: none; 64 | } 65 | .alertify-log { 66 | display: block; 67 | margin-top: 10px; 68 | position: relative; 69 | right: -300px; 70 | opacity: 0; 71 | } 72 | .alertify-log-show { 73 | right: 0; 74 | opacity: 1; 75 | } 76 | .alertify-log-hide { 77 | -webkit-transform: translate(300px, 0); 78 | -moz-transform: translate(300px, 0); 79 | -ms-transform: translate(300px, 0); 80 | -o-transform: translate(300px, 0); 81 | transform: translate(300px, 0); 82 | opacity: 0; 83 | } 84 | .alertify-dialog { 85 | padding: 25px; 86 | } 87 | .alertify-resetFocus { 88 | border: 0; 89 | clip: rect(0 0 0 0); 90 | height: 1px; 91 | margin: -1px; 92 | overflow: hidden; 93 | padding: 0; 94 | position: absolute; 95 | width: 1px; 96 | } 97 | .alertify-inner { 98 | text-align: center; 99 | } 100 | .alertify-text { 101 | margin-bottom: 15px; 102 | width: 100%; 103 | -webkit-box-sizing: border-box; 104 | -moz-box-sizing: border-box; 105 | box-sizing: border-box; 106 | font-size: 100%; 107 | } 108 | .alertify-buttons { 109 | } 110 | .alertify-button, 111 | .alertify-button:hover, 112 | .alertify-button:active, 113 | .alertify-button:visited { 114 | background: none; 115 | text-decoration: none; 116 | border: none; 117 | /* line-height and font-size for input button */ 118 | line-height: 1.5; 119 | font-size: 100%; 120 | display: inline-block; 121 | cursor: pointer; 122 | margin-left: 5px; 123 | } 124 | 125 | @media only screen and (max-width: 680px) { 126 | .alertify, 127 | .alertify-logs { 128 | width: 90%; 129 | -webkit-box-sizing: border-box; 130 | -moz-box-sizing: border-box; 131 | box-sizing: border-box; 132 | } 133 | .alertify { 134 | left: 5%; 135 | margin: 0; 136 | } 137 | } 138 | --------------------------------------------------------------------------------