├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── BovsiTimeClock.code-workspace ├── LICENSE ├── README.md └── src ├── .vscode ├── launch.json └── tasks.json ├── AspNetCoreApiStarter.sln ├── Tests ├── Web.Api.Core.UnitTests │ ├── Domain │ │ └── Entities │ │ │ └── UserUnitTests.cs │ ├── UseCases │ │ ├── ExchangeRefreshTokenUseCaseUnitTests.cs │ │ ├── LoginUseCaseUnitTests.cs │ │ └── RegisterUserUseCaseUnitTests.cs │ └── Web.Api.Core.UnitTests.csproj ├── Web.Api.Infrastructure.UnitTests │ ├── Auth │ │ └── JwtFactoryUnitTests.cs │ └── Web.Api.Infrastructure.UnitTests.csproj ├── Web.Api.IntegrationTests │ ├── Controllers │ │ ├── AccountsControllerIntegrationTests.cs │ │ └── AuthControllerIntegrationTests.cs │ ├── CustomWebApplicationFactory.cs │ ├── SeedData.cs │ └── Web.Api.IntegrationTests.csproj └── Web.Api.UnitTests │ ├── Presenters │ ├── ExchangeRefreshTokenPresenterUnitTests.cs │ ├── LoginPresenterUnitTests.cs │ └── RegisterUserPresenterUnitTests.cs │ └── Web.Api.UnitTests.csproj ├── Web.Api.Core ├── AssemblyInfo.cs ├── CoreModule.cs ├── Domain │ ├── Entities │ │ ├── Activity.cs │ │ ├── Client.cs │ │ ├── Employee.cs │ │ ├── Employer.cs │ │ ├── RefreshToken.cs │ │ ├── Supervisor.cs │ │ ├── User.cs │ │ └── WorkActivity.cs │ └── Enums │ │ └── EmployeeType.cs ├── Dto │ ├── AccessToken.cs │ ├── Error.cs │ ├── GatewayResponses │ │ ├── BaseGatewayResponse.cs │ │ └── Repositories │ │ │ ├── CreateEntityResponse.cs │ │ │ └── CreateUserResponse.cs │ ├── UseCaseRequests │ │ ├── CreateActivityRequest.cs │ │ ├── CreateClientRequest.cs │ │ ├── CreateEmployerRequest.cs │ │ ├── ExchangeRefreshTokenRequest.cs │ │ ├── LoginRequest.cs │ │ └── RegisterUserRequest.cs │ └── UseCaseResponses │ │ ├── ExchangeRefreshTokenResponse.cs │ │ ├── LoginResponse.cs │ │ └── RegisterUserResponse.cs ├── Interfaces │ ├── Gateways │ │ └── Repositories │ │ │ ├── IActivitiesRepository.cs │ │ │ ├── IClientsRepository.cs │ │ │ ├── IEmployeesRepository.cs │ │ │ ├── IEmployersRepository.cs │ │ │ ├── IRepository.cs │ │ │ ├── ISpecification.cs │ │ │ ├── ISupervisorsRepository.cs │ │ │ └── IUserRepository.cs │ ├── IOutputPort.cs │ ├── IUseCaseRequest.cs │ ├── IUseCaseRequestHandler.cs │ ├── Services │ │ ├── IActivityService.cs │ │ ├── IClientService.cs │ │ ├── IEmployerService.cs │ │ ├── IJwtFactory.cs │ │ ├── IJwtTokenValidator.cs │ │ ├── ILogger.cs │ │ └── ITokenFactory.cs │ ├── UseCaseResponseMessage.cs │ └── UseCases │ │ ├── IExchangeRefreshTokenUseCase.cs │ │ ├── ILoginUseCase.cs │ │ └── IRegisterUserUseCase.cs ├── Shared │ └── BaseEntity.cs ├── Specifications │ ├── BaseSpecification.cs │ └── UserSpecification.cs ├── UseCases │ ├── ExchangeRefreshTokenUseCase.cs │ ├── LoginUseCase.cs │ └── RegisterUserUseCase.cs └── Web.Api.Core.csproj ├── Web.Api.Infrastructure ├── AssemblyInfo.cs ├── Auth │ ├── JwtFactory.cs │ ├── JwtIssuerOptions.cs │ ├── JwtTokenHandler.cs │ ├── JwtTokenValidator.cs │ └── TokenFactory.cs ├── Data │ ├── AppDbContext.cs │ ├── AppDbContextExtensions.cs │ ├── AppDbContextFactory.cs │ ├── Mapping │ │ └── Profiles.cs │ ├── Repositories │ │ ├── ActivitiesRepository.cs │ │ ├── ClientsRepository.cs │ │ ├── EfRepository.cs │ │ ├── EmployeesRepository.cs │ │ ├── EmployersRepository.cs │ │ ├── SupervisorsRepository.cs │ │ └── UserRepository.cs │ ├── Seeds │ │ └── DbSeeder.cs │ └── Services │ │ ├── ActivityService.cs │ │ ├── ClientService.cs │ │ └── EmployerService.cs ├── Helpers │ └── Constants.cs ├── Identity │ ├── AppIdentityDbContext.cs │ ├── AppIdentityDbContextFactory.cs │ └── AppUser.cs ├── InfrastructureModule.cs ├── Interfaces │ └── IJwtTokenHandler.cs ├── InternalConstructorFinder.cs ├── Logging │ └── Logger.cs ├── Migrations │ ├── 20181105222832_Initial.Designer.cs │ ├── 20181105222832_Initial.cs │ ├── 20181107211824_Relations.Designer.cs │ ├── 20181107211824_Relations.cs │ ├── AppDbContextModelSnapshot.cs │ └── AppIdentityDb │ │ ├── 20181105222901_Initial.Designer.cs │ │ ├── 20181105222901_Initial.cs │ │ └── AppIdentityDbContextModelSnapshot.cs ├── README.md ├── Shared │ └── DesignTimeDbContextFactoryBase.cs ├── Web.Api.Infrastructure.csproj ├── appsettings.Designer.cs └── appsettings.json ├── Web.Api ├── Controllers │ ├── AccountsController.cs │ ├── ActivitiesController.cs │ ├── AuthController.cs │ ├── ClientsController.cs │ ├── EmployeesController.cs │ ├── EmployersController.cs │ └── SupervisorsController.cs ├── Extensions │ └── ResponseExtensions.cs ├── Helpers │ ├── SecretChecker.cs │ └── Strings.cs ├── Models │ ├── Request │ │ ├── ExchangeRefreshTokenRequest.cs │ │ ├── LoginRequest.cs │ │ └── RegisterUserRequest.cs │ ├── Response │ │ ├── ExchangeRefreshTokenResponse.cs │ │ └── LoginResponse.cs │ ├── Settings │ │ └── AuthSettings.cs │ └── Validation │ │ ├── ExchangeRefreshTokenRequestValidator.cs │ │ ├── LoginRequestValidator.cs │ │ └── RegisterUserRequestValidator.cs ├── Presenters │ ├── ExchangeRefreshTokenPresenter.cs │ ├── JsonContentResult.cs │ ├── LoginPresenter.cs │ └── RegisterUserPresenter.cs ├── Program.cs ├── Serialization │ └── JsonSerializer.cs ├── Startup.cs ├── Web.Api.csproj ├── appsettings.json ├── c:\temp\logs\internallog.txt ├── nlog.config └── wwwroot │ └── swagger-ui │ ├── feelingblue.css │ ├── flattop.css │ ├── material.css │ ├── monokai.css │ ├── muted.css │ ├── newspaper.css │ └── outline.css └── WebServer ├── .gitignore ├── AngularApp ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routing.ts │ │ ├── auth.guard.ts │ │ ├── components │ │ │ ├── error │ │ │ │ └── page-not-found │ │ │ │ │ ├── page-not-found.component.css │ │ │ │ │ ├── page-not-found.component.html │ │ │ │ │ ├── page-not-found.component.spec.ts │ │ │ │ │ └── page-not-found.component.ts │ │ │ └── home │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.scss │ │ │ │ └── home.component.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ ├── http │ │ │ │ └── abstractRestService.service.ts │ │ │ ├── index.ts │ │ │ ├── logger │ │ │ │ ├── consoleLogger.service.ts │ │ │ │ └── logger.service.ts │ │ │ └── top-nav-menu │ │ │ │ ├── top-nav-menu.component.html │ │ │ │ ├── top-nav-menu.component.scss │ │ │ │ ├── top-nav-menu.component.spec.ts │ │ │ │ └── top-nav-menu.component.ts │ │ ├── directives │ │ │ ├── emailValidator.directive.ts │ │ │ └── focus.directive.ts │ │ ├── features │ │ │ ├── account │ │ │ │ ├── account.module.ts │ │ │ │ ├── account.routing.ts │ │ │ │ ├── loginForm │ │ │ │ │ ├── loginForm.component.html │ │ │ │ │ ├── loginForm.component.scss │ │ │ │ │ └── loginForm.component.ts │ │ │ │ └── registrationForm │ │ │ │ │ ├── registrationForm.component.html │ │ │ │ │ ├── registrationForm.component.scss │ │ │ │ │ └── registrationForm.component.ts │ │ │ ├── dashboard │ │ │ │ ├── accountHome │ │ │ │ │ ├── accountHome.component.html │ │ │ │ │ ├── accountHome.component.scss │ │ │ │ │ └── accountHome.component.ts │ │ │ │ ├── dashboard.module.ts │ │ │ │ ├── dashboard.routing.ts │ │ │ │ ├── home │ │ │ │ │ ├── home.component.css │ │ │ │ │ ├── home.component.html │ │ │ │ │ ├── home.component.spec.ts │ │ │ │ │ └── home.component.ts │ │ │ │ └── root │ │ │ │ │ ├── root.component.html │ │ │ │ │ ├── root.component.scss │ │ │ │ │ └── root.component.ts │ │ │ ├── employer │ │ │ │ ├── clients │ │ │ │ │ ├── activity │ │ │ │ │ │ ├── activity.module.ts │ │ │ │ │ │ ├── activity.routing.ts │ │ │ │ │ │ ├── activityList │ │ │ │ │ │ │ ├── activityList.component.html │ │ │ │ │ │ │ ├── activityList.component.scss │ │ │ │ │ │ │ └── activityList.component.ts │ │ │ │ │ │ ├── activityUpsert │ │ │ │ │ │ │ ├── activityUpsert.component.html │ │ │ │ │ │ │ ├── activityUpsert.component.scss │ │ │ │ │ │ │ └── activityUpsert.component.ts │ │ │ │ │ │ ├── root │ │ │ │ │ │ │ ├── activity.component.html │ │ │ │ │ │ │ ├── activity.component.scss │ │ │ │ │ │ │ └── activity.component.ts │ │ │ │ │ │ └── services │ │ │ │ │ │ │ └── activity.service.ts │ │ │ │ │ ├── clients.module.ts │ │ │ │ │ ├── clients.routing.ts │ │ │ │ │ ├── clientsList │ │ │ │ │ │ ├── clientsList.component.html │ │ │ │ │ │ ├── clientsList.component.scss │ │ │ │ │ │ └── clientsList.component.ts │ │ │ │ │ ├── clientsUpsert │ │ │ │ │ │ ├── clientsUpsert.component.html │ │ │ │ │ │ ├── clientsUpsert.component.scss │ │ │ │ │ │ └── clientsUpsert.component.ts │ │ │ │ │ ├── root │ │ │ │ │ │ ├── client.component.html │ │ │ │ │ │ ├── client.component.scss │ │ │ │ │ │ └── client.component.ts │ │ │ │ │ └── services │ │ │ │ │ │ └── clients.service.ts │ │ │ │ ├── employer.module.ts │ │ │ │ ├── employer.routing.ts │ │ │ │ ├── employerList │ │ │ │ │ ├── employerList.component.html │ │ │ │ │ ├── employerList.component.scss │ │ │ │ │ └── employerList.component.ts │ │ │ │ ├── employerUpsert │ │ │ │ │ ├── employerUpsert.component.html │ │ │ │ │ ├── employerUpsert.component.scss │ │ │ │ │ └── employerUpsert.component.ts │ │ │ │ ├── root │ │ │ │ │ ├── employer.component.html │ │ │ │ │ ├── employer.component.scss │ │ │ │ │ └── employer.component.ts │ │ │ │ └── services │ │ │ │ │ └── employer.service.ts │ │ │ └── timeTracker │ │ │ │ ├── components │ │ │ │ └── timeTracker │ │ │ │ │ ├── timeTracker.component.html │ │ │ │ │ ├── timeTracker.component.scss │ │ │ │ │ ├── timeTracker.component.spec.ts │ │ │ │ │ └── timeTracker.component.ts │ │ │ │ ├── services │ │ │ │ ├── activities.service.ts │ │ │ │ └── clients.service.ts │ │ │ │ ├── timeTracker.module.ts │ │ │ │ └── timeTracker.routing.ts │ │ ├── models │ │ │ ├── activity.model.ts │ │ │ ├── client.model.ts │ │ │ ├── employer.model.ts │ │ │ └── entity.model.ts │ │ ├── services │ │ │ ├── bearer.interceptor.ts │ │ │ └── refreshToken.interceptor.ts │ │ └── shared │ │ │ ├── loading-spinner │ │ │ ├── loading-spinner.component.html │ │ │ ├── loading-spinner.component.scss │ │ │ ├── loading-spinner.component.spec.ts │ │ │ └── loading-spinner.component.ts │ │ │ ├── material │ │ │ └── material.module.ts │ │ │ ├── models │ │ │ ├── credentials.interface.ts │ │ │ └── userRegistration.interface.ts │ │ │ ├── notifications │ │ │ ├── index.ts │ │ │ ├── notifStyle.enum.ts │ │ │ ├── notificiation.service.ts │ │ │ └── snackBarMessage.model.ts │ │ │ ├── services │ │ │ ├── base.service.ts │ │ │ └── user.service.ts │ │ │ ├── shared.module.spec.ts │ │ │ └── shared.module.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── images │ │ │ └── bovsi-owl-500x250.png │ │ └── mdi.svg │ ├── bovsi-dark-theme.scss │ ├── bovsi-theme.scss │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json ├── Controllers └── SampleDataController.cs ├── Helpers └── Strings.cs ├── Pages ├── Error.cshtml ├── Error.cshtml.cs └── _ViewImports.cshtml ├── Program.cs ├── Startup.cs ├── WebServer.csproj ├── appsettings.json ├── package-lock.json └── wwwroot └── favicon.ico /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/Web.Api/bin/Debug/netcoreapp2.1/Web.Api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src/Web.Api", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "http://localhost:5000/swagger", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start http://localhost:5000/swagger" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/Web.Api/Web.Api.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /BovsiTimeClock.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mark Macneil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AspNetCoreApiStarter 2 | An ASP.NET Core (v2.1) Web API project to quickly bootstrap new projects. Includes Identity, JWT authentication w/ refresh tokens. 3 | 4 | [Original Github repo](https://github.com/mmacneil/AspNetCoreApiStarter) -- Original github repo that this template came from. 5 | 6 | [Repo's Blog write-up by FullStack Mark](https://fullstackmark.com/post/19/jwt-authentication-flow-with-refresh-tokens-in-aspnet-core-web-api) 7 | 8 | # Setup 9 | - Uses Sql Server Express LocalDB (If using Visual Studio install it under Individual Components in the Visual Studio installer or install separately using [this link](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-2016-express-localdb?view=sql-server-2017). 10 | - Apply database migrations to create the db. From a command line within the *Web.Api.Infrastructure* project folder use the dotnet CLI to run : 11 | - Web.Api.Infrastructure>**dotnet ef database update --context AppDbContext** 12 | - Web.Api.Infrastructure>**dotnet ef database update --context AppIdentityDbContext** 13 | 14 | # Visual Studio 15 | Open the solution file AspNetCoreApiStarter.sln and build/run. 16 | 17 | # Visual Studio Code 18 | Open the src folder and F5 to build/run. 19 | 20 | # Swagger Enabled 21 | To explore and test the available APIs simply run the project and use the Swagger UI. 22 | 23 | The available APIs include: 24 | - POST `/api/accounts` - Creates a new user. 25 | - POST `/api/auth/login` - Authenticates a user. 26 | - POST `/api/auth/refreshtoken` - Refreshes expired access tokens. 27 | - GET `/api/protected` - Protected controller for testing role-based authorization. 28 | 29 | # Contact 30 | mark@fullstackmark.com 31 | 32 | -------------------------------------------------------------------------------- /src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Web.Api/bin/Debug/netcoreapp2.1/Web.Api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Web.Api", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "https://localhost:5002/swagger", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start https://localhost:5002/swagger" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Web.Api/Web.Api.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /src/Tests/Web.Api.Core.UnitTests/Domain/Entities/UserUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Web.Api.Core.Domain.Entities; 3 | using Xunit; 4 | 5 | namespace Web.Api.Core.UnitTests.Domain.Entities 6 | { 7 | public class UserUnitTests 8 | { 9 | [Fact] 10 | public void HasValidRefreshToken_GivenValidToken_ShouldReturnTrue() 11 | { 12 | // arrange 13 | const string refreshToken = "1234"; 14 | var user = new User("", "", "", ""); 15 | user.AddRereshToken(refreshToken, Guid.NewGuid(), "127.0.0.1"); 16 | 17 | // act 18 | var result = user.HasValidRefreshToken(refreshToken); 19 | 20 | Assert.True(result); 21 | } 22 | 23 | [Fact] 24 | public void HasValidRefreshToken_GivenExpiredToken_ShouldReturnFalse() 25 | { 26 | // arrange 27 | const string refreshToken = "1234"; 28 | var user = new User("", "", "", ""); 29 | user.AddRereshToken(refreshToken, Guid.NewGuid(), "127.0.0.1", -6); // Provision with token 6 days old 30 | 31 | // act 32 | var result = user.HasValidRefreshToken(refreshToken); 33 | 34 | Assert.False(result); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.Core.UnitTests/UseCases/RegisterUserUseCaseUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 3 | using Web.Api.Core.Dto.UseCaseRequests; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Core.Interfaces; 6 | using Web.Api.Core.Interfaces.Gateways.Repositories; 7 | using Web.Api.Core.UseCases; 8 | using Xunit; 9 | 10 | namespace Web.Api.Core.UnitTests.UseCases 11 | { 12 | public class RegisterUserUseCaseUnitTests 13 | { 14 | [Fact] 15 | public async void Handle_GivenValidRegistrationDetails_ShouldSucceed() 16 | { 17 | // arrange 18 | 19 | // 1. We need to store the user data somehow 20 | var mockUserRepository = new Mock(); 21 | mockUserRepository 22 | .Setup(repo => repo.Create(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) 23 | .ReturnsAsync(new CreateUserResponse("", true)); 24 | 25 | // 2. The use case and star of this test 26 | var useCase = new RegisterUserUseCase(mockUserRepository.Object); 27 | 28 | // 3. The output port is the mechanism to pass response data from the use case to a Presenter 29 | // for final preparation to deliver back to the UI/web page/api response etc. 30 | var mockOutputPort = new Mock>(); 31 | mockOutputPort.Setup(outputPort => outputPort.Handle(It.IsAny())); 32 | 33 | // act 34 | 35 | // 4. We need a request model to carry data into the use case from the upper layer (UI, Controller etc.) 36 | var response = await useCase.Handle(new RegisterUserRequest("firstName", "lastName", "me@domain.com", "userName", "password"), mockOutputPort.Object); 37 | 38 | // assert 39 | Assert.True(response); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.Core.UnitTests/Web.Api.Core.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.Infrastructure.UnitTests/Auth/JwtFactoryUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Text; 4 | using Microsoft.Extensions.Options; 5 | using Microsoft.IdentityModel.Tokens; 6 | using Moq; 7 | using Web.Api.Infrastructure.Auth; 8 | using Web.Api.Infrastructure.Interfaces; 9 | using Xunit; 10 | 11 | namespace Web.Api.Infrastructure.UnitTests.Auth 12 | { 13 | public class JwtFactoryUnitTests 14 | { 15 | [Fact] 16 | public async void GenerateEncodedToken_GivenValidInputs_ReturnsExpectedTokenData() 17 | { 18 | // arrange 19 | var token = Guid.NewGuid().ToString(); 20 | var id = Guid.NewGuid().ToString(); 21 | var jwtIssuerOptions = new JwtIssuerOptions 22 | { 23 | Issuer = "", 24 | Audience = "", 25 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret_key")),SecurityAlgorithms.HmacSha256) 26 | }; 27 | 28 | var mockJwtTokenHandler = new Mock(); 29 | mockJwtTokenHandler.Setup(handler => handler.WriteToken(It.IsAny())).Returns(token); 30 | 31 | var jwtFactory = new JwtFactory(mockJwtTokenHandler.Object,Options.Create(jwtIssuerOptions)); 32 | 33 | // act 34 | var result = await jwtFactory.GenerateEncodedToken(id, "userName"); 35 | 36 | // assert 37 | Assert.Equal(token,result.Token); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.Infrastructure.UnitTests/Web.Api.Infrastructure.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.IntegrationTests/Controllers/AccountsControllerIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using Web.Api.Core.Dto.UseCaseRequests; 8 | using Xunit; 9 | 10 | namespace Web.Api.IntegrationTests.Controllers 11 | { 12 | public class AccountsControllerIntegrationTests : IClassFixture> 13 | { 14 | private readonly HttpClient _client; 15 | 16 | public AccountsControllerIntegrationTests(CustomWebApplicationFactory factory) 17 | { 18 | _client = factory.CreateClient(); 19 | } 20 | 21 | [Fact] 22 | public async Task CanRegisterUserWithValidAccountDetails() 23 | { 24 | var httpResponse = await _client.PostAsync("/api/accounts", new StringContent(JsonConvert.SerializeObject(new RegisterUserRequest("John", "Doe", "jdoe@gmail.com", "johndoe", "Pa$$word1")), Encoding.UTF8, "application/json")); 25 | httpResponse.EnsureSuccessStatusCode(); 26 | var stringResponse = await httpResponse.Content.ReadAsStringAsync(); 27 | dynamic result = JObject.Parse(stringResponse); 28 | Assert.True((bool) result.success); 29 | Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); 30 | } 31 | 32 | [Fact] 33 | public async Task CantRegisterUserWithInvalidAccountDetails() 34 | { 35 | var httpResponse = await _client.PostAsync("/api/accounts", new StringContent(JsonConvert.SerializeObject(new RegisterUserRequest("John", "Doe", "", "johndoe", "Pa$$word1")), Encoding.UTF8, "application/json")); 36 | var stringResponse = await httpResponse.Content.ReadAsStringAsync(); 37 | Assert.Contains("'Email' is not a valid email address.", stringResponse); 38 | Assert.Equal(HttpStatusCode.BadRequest, httpResponse.StatusCode); 39 | } 40 | } 41 | } 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.IntegrationTests/SeedData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Web.Api.Core.Domain.Entities; 3 | using Web.Api.Infrastructure.Data; 4 | using Web.Api.Infrastructure.Identity; 5 | 6 | 7 | namespace Web.Api.IntegrationTests 8 | { 9 | public static class SeedData 10 | { 11 | public static void PopulateTestData(AppIdentityDbContext dbContext) 12 | { 13 | dbContext.Users.Add(new AppUser 14 | { 15 | Id = "41532945-599e-4910-9599-0e7402017fbe", 16 | UserName = "mmacneil", 17 | NormalizedUserName = "MMACNEIL", 18 | Email = "mark@fullstackmark.com", 19 | NormalizedEmail = "MARK@FULLSTACKMARK.COM", 20 | PasswordHash = "AQAAAAEAACcQAAAAEKQBX+Qqr/M3qmEcoM3Y/M/8XtVKZ9RnaiXlamue6MIuhOoYONHS7BUHkmOxpF/X3w==", 21 | SecurityStamp = "YIJZLWUFIIDD3IZSFDD7OQWG6D4QIYPB", 22 | ConcurrencyStamp = "e432007d-0a54-4332-9212-ca9d7e757275" 23 | }); 24 | 25 | dbContext.SaveChanges(); 26 | } 27 | 28 | public static void PopulateTestData(AppDbContext dbContext) 29 | { 30 | var user = new User("Mark", "Macneil", "41532945-599e-4910-9599-0e7402017fbe", "mmacneil"); 31 | user.AddRereshToken("rB1afdEe6MWu6TyN8zm58xqt/3KWOLRAah2nHLWcboA=", Guid.NewGuid(), "127.0.0.1"); 32 | dbContext.Users.Add(user); 33 | dbContext.SaveChanges(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.IntegrationTests/Web.Api.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.UnitTests/Presenters/ExchangeRefreshTokenPresenterUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Newtonsoft.Json; 3 | using Web.Api.Core.Dto; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Presenters; 6 | using Xunit; 7 | 8 | namespace Web.Api.UnitTests.Presenters 9 | { 10 | public class ExchangeRefreshTokenPresenterUnitTests 11 | { 12 | [Fact] 13 | public void Handle_GivenSuccessfulUseCaseResponse_SetsAccessToken() 14 | { 15 | // arrange 16 | const string token = "777888AAABBB"; 17 | var presenter = new ExchangeRefreshTokenPresenter(); 18 | 19 | // act 20 | presenter.Handle(new ExchangeRefreshTokenResponse(new AccessToken(token, 0), "", true)); 21 | 22 | // assert 23 | dynamic data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 24 | Assert.Equal(token, data.accessToken.token.Value); 25 | } 26 | 27 | [Fact] 28 | public void Handle_GivenSuccessfulUseCaseResponse_SetsRefreshToken() 29 | { 30 | // arrange 31 | const string token = "777888AAABBB"; 32 | var presenter = new ExchangeRefreshTokenPresenter(); 33 | 34 | // act 35 | presenter.Handle(new ExchangeRefreshTokenResponse(null, token, true)); 36 | 37 | // assert 38 | dynamic data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 39 | Assert.Equal(token, data.refreshToken.Value); 40 | } 41 | 42 | [Fact] 43 | public void Handle_GivenFailedUseCaseResponse_SetsError() 44 | { 45 | // arrange 46 | var presenter = new ExchangeRefreshTokenPresenter(); 47 | 48 | // act 49 | presenter.Handle(new ExchangeRefreshTokenResponse(false,"Invalid Token.")); 50 | 51 | // assert 52 | var data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 53 | Assert.Equal((int)HttpStatusCode.BadRequest, presenter.ContentResult.StatusCode); 54 | Assert.Equal("Invalid Token.", data); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.UnitTests/Presenters/LoginPresenterUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using Newtonsoft.Json; 5 | using Web.Api.Core.Dto; 6 | using Web.Api.Core.Dto.UseCaseResponses; 7 | using Web.Api.Presenters; 8 | using Xunit; 9 | 10 | namespace Web.Api.UnitTests.Presenters 11 | { 12 | public class LoginPresenterUnitTests 13 | { 14 | [Fact] 15 | public void Handle_GivenSuccessfulUseCaseResponse_SetsOKHttpStatusCode() 16 | { 17 | // arrange 18 | var presenter = new LoginPresenter(); 19 | 20 | // act 21 | presenter.Handle(new LoginResponse(new AccessToken("", 0),"", true)); 22 | 23 | // assert 24 | Assert.Equal((int)HttpStatusCode.OK, presenter.ContentResult.StatusCode); 25 | } 26 | 27 | [Fact] 28 | public void Handle_GivenSuccessfulUseCaseResponse_SetsAccessToken() 29 | { 30 | // arrange 31 | const string token = "777888AAABBB"; 32 | var presenter = new LoginPresenter(); 33 | 34 | // act 35 | presenter.Handle(new LoginResponse(new AccessToken(token, 0),"", true)); 36 | 37 | // assert 38 | dynamic data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 39 | Assert.Equal(token, data.accessToken.token.Value); 40 | } 41 | 42 | [Fact] 43 | public void Handle_GivenFailedUseCaseResponse_SetsErrors() 44 | { 45 | // arrange 46 | var presenter = new LoginPresenter(); 47 | 48 | // act 49 | presenter.Handle(new LoginResponse(new[] { new Error("", "Invalid username/password") })); 50 | 51 | // assert 52 | var data = JsonConvert.DeserializeObject>(presenter.ContentResult.Content); 53 | Assert.Equal((int)HttpStatusCode.Unauthorized, presenter.ContentResult.StatusCode); 54 | Assert.Equal("Invalid username/password", data.First().Description); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.UnitTests/Presenters/RegisterUserPresenterUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Newtonsoft.Json; 3 | using Web.Api.Core.Dto.UseCaseResponses; 4 | using Web.Api.Presenters; 5 | using Xunit; 6 | 7 | namespace Web.Api.UnitTests.Presenters 8 | { 9 | public class RegisterUserPresenterUnitTests 10 | { 11 | [Fact] 12 | public void Handle_GivenSuccessfulUseCaseResponse_SetsOKHttpStatusCode() 13 | { 14 | // arrange 15 | var presenter = new RegisterUserPresenter(); 16 | 17 | // act 18 | presenter.Handle(new RegisterUserResponse("", true)); 19 | 20 | // assert 21 | Assert.Equal((int)HttpStatusCode.OK, presenter.ContentResult.StatusCode); 22 | } 23 | 24 | [Fact] 25 | public void Handle_GivenSuccessfulUseCaseResponse_SetsId() 26 | { 27 | // arrange 28 | var presenter = new RegisterUserPresenter(); 29 | 30 | // act 31 | presenter.Handle(new RegisterUserResponse("1234", true)); 32 | 33 | // assert 34 | dynamic data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 35 | Assert.True(data.success.Value); 36 | Assert.Equal("1234", data.id.Value); 37 | } 38 | 39 | [Fact] 40 | public void Handle_GivenFailedUseCaseResponse_SetsErrors() 41 | { 42 | // arrange 43 | var presenter = new RegisterUserPresenter(); 44 | 45 | // act 46 | presenter.Handle(new RegisterUserResponse(new[] { "missing first name" })); 47 | 48 | // assert 49 | dynamic data = JsonConvert.DeserializeObject(presenter.ContentResult.Content); 50 | Assert.False(data.success.Value); 51 | Assert.Equal("missing first name", data.errors.First.Value); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Tests/Web.Api.UnitTests/Web.Api.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Web.Api.Core/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("Web.Api.Infrastructure"), InternalsVisibleTo("Web.Api.Core.UnitTests"),InternalsVisibleTo("Web.Api.IntegrationTests")] -------------------------------------------------------------------------------- /src/Web.Api.Core/CoreModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Web.Api.Core.Interfaces.UseCases; 3 | using Web.Api.Core.UseCases; 4 | 5 | namespace Web.Api.Core 6 | { 7 | public class CoreModule : Module 8 | { 9 | protected override void Load(ContainerBuilder builder) 10 | { 11 | builder.RegisterType().As().InstancePerLifetimeScope(); 12 | builder.RegisterType().As().InstancePerLifetimeScope(); 13 | builder.RegisterType().As().InstancePerLifetimeScope(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/Activity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Web.Api.Core.Shared; 4 | 5 | namespace Web.Api.Core.Domain.Entities 6 | { 7 | public class Activity : BaseEntity 8 | { 9 | public string Name { get; set; } 10 | public string Description { get; set; } 11 | [DataMemberAttribute] 12 | public DateTime DueDate { get; set; } 13 | public Guid ClientId { get; set; } 14 | internal Activity() { /* Required by EF */ } 15 | 16 | public Activity(string name, string description, DateTime dueDate) 17 | { 18 | this.Name = name; 19 | this.Description = description; 20 | this.DueDate = dueDate; 21 | this.Created = DateTime.UtcNow; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/Client.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Web.Api.Core.Shared; 4 | 5 | namespace Web.Api.Core.Domain.Entities 6 | { 7 | public class Client : BaseEntity 8 | { 9 | [Required] 10 | public string Name { get; set; } 11 | public string Description { get; set; } 12 | // public int EmployerId { get; set; } 13 | // public Employer Employer { get; set; } 14 | public Guid EmployerId { get; set; } 15 | 16 | internal Client() {/* Required by EF */} 17 | public Client(string name) 18 | { 19 | this.Name = name; 20 | this.Created = DateTime.UtcNow; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/Employee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using Web.Api.Core.Domain.Enums; 5 | using Web.Api.Core.Shared; 6 | 7 | namespace Web.Api.Core.Domain.Entities 8 | { 9 | public class Employee : BaseEntity 10 | { 11 | [Required] 12 | [Display(Name = "First Name")] 13 | [StringLength(70, ErrorMessage = "First name cannot be longer than 70 characters.")] 14 | public string FirstName { get; set; } 15 | [Required] 16 | [Display(Name = "Last Name")] 17 | [StringLength(70, ErrorMessage = "Last name cannot be longer than 70 characters.")] 18 | public string LastName { get; set; } 19 | // public EmployeeType EmployeeType { get; set; } 20 | [Display(Name = "Full Name")] 21 | public string FullName 22 | { 23 | get 24 | { 25 | return $"{FirstName} {LastName}"; 26 | } 27 | } 28 | public Guid EmployerId { get; set; } 29 | 30 | internal Employee() { /* Required by EF */ } 31 | 32 | public Employee(string firstName, string lastName) 33 | { 34 | this.FirstName = firstName; 35 | this.LastName = lastName; 36 | // this.EmployeeType = employeeType; 37 | this.Created = DateTime.UtcNow; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/Employer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using Web.Api.Core.Shared; 6 | 7 | 8 | namespace Web.Api.Core.Domain.Entities 9 | { 10 | public class Employer : BaseEntity 11 | { 12 | [Required] 13 | public string Name { get; set; } // EF migrations require at least private setter - won't work on auto-property 14 | public string Description { get; set; } 15 | // public ICollection Employees { get; set; } 16 | // public ICollection Clients { get; set; } 17 | 18 | 19 | internal Employer() { /* Required by EF */ } 20 | 21 | public Employer(string name) 22 | { 23 | this.Name = name; 24 | this.Created = DateTime.UtcNow; 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/RefreshToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Web.Api.Core.Shared; 3 | 4 | 5 | namespace Web.Api.Core.Domain.Entities 6 | { 7 | public class RefreshToken : BaseEntity 8 | { 9 | public string Token { get; private set; } 10 | public DateTime Expires { get; private set; } 11 | public Guid UserId { get; private set; } 12 | public bool Active => DateTime.UtcNow <= Expires; 13 | public string RemoteIpAddress { get; private set; } 14 | 15 | public RefreshToken(string token, DateTime expires, Guid userId, string remoteIpAddress) 16 | { 17 | Token = token; 18 | Expires = expires; 19 | UserId = userId; 20 | RemoteIpAddress = remoteIpAddress; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/Supervisor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Web.Api.Core.Shared; 3 | 4 | namespace Web.Api.Core.Domain.Entities 5 | { 6 | public class Supervisor : BaseEntity 7 | { 8 | public string FirstName { get; set; } 9 | public string LastName { get; set; } 10 | internal Supervisor() { /* Required by EF */ } 11 | 12 | public Supervisor(string firstName, string lastName) 13 | { 14 | this.FirstName = firstName; 15 | this.LastName = lastName; 16 | this.Created = DateTime.UtcNow; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Web.Api.Core.Shared; 5 | 6 | 7 | namespace Web.Api.Core.Domain.Entities 8 | { 9 | public class User : BaseEntity 10 | { 11 | public string FirstName { get; private set; } // EF migrations require at least private setter - won't work on auto-property 12 | public string LastName { get; private set; } 13 | public string IdentityId { get; private set; } 14 | public string UserName { get; private set; } // Required by automapper 15 | public string Email { get; private set; } 16 | public string PasswordHash { get; private set; } 17 | 18 | private readonly List _refreshTokens = new List(); 19 | public IReadOnlyCollection RefreshTokens => _refreshTokens.AsReadOnly(); 20 | 21 | internal User() { /* Required by EF */ } 22 | 23 | internal User(string firstName, string lastName, string identityId, string userName) 24 | { 25 | FirstName = firstName; 26 | LastName = lastName; 27 | IdentityId = identityId; 28 | UserName = userName; 29 | } 30 | 31 | public bool HasValidRefreshToken(string refreshToken) 32 | { 33 | return _refreshTokens.Any(rt => rt.Token == refreshToken && rt.Active); 34 | } 35 | 36 | public void AddRereshToken(string token, Guid userId, string remoteIpAddress, double daysToExpire = 5) 37 | { 38 | _refreshTokens.Add(new RefreshToken(token, DateTime.UtcNow.AddDays(daysToExpire), userId, remoteIpAddress)); 39 | } 40 | 41 | public void RemoveRefreshToken(string refreshToken) 42 | { 43 | _refreshTokens.Remove(_refreshTokens.First(t => t.Token == refreshToken)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Entities/WorkActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Web.Api.Core.Shared; 3 | 4 | namespace Web.Api.Core.Domain.Entities 5 | { 6 | public class WorkActivity : BaseEntity 7 | { 8 | public int ClientId { get; set; } 9 | public decimal TotalWorkTime { get; set; } 10 | public string Notes { get; set; } 11 | internal WorkActivity() { /* Required by EF */ } 12 | 13 | public WorkActivity(int clientId, decimal totalWorkTime, string notes) 14 | { 15 | this.ClientId = clientId; 16 | this.TotalWorkTime = totalWorkTime; 17 | this.Notes = notes; 18 | this.Created = DateTime.UtcNow; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Domain/Enums/EmployeeType.cs: -------------------------------------------------------------------------------- 1 | namespace Web.Api.Core.Domain.Enums 2 | { 3 | public enum EmployeeType 4 | { 5 | Undefined = 0, 6 | Basic, 7 | Supervisor 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/AccessToken.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Core.Dto 4 | { 5 | public sealed class AccessToken 6 | { 7 | public string Token { get; } 8 | public int ExpiresIn { get; } 9 | 10 | public AccessToken(string token, int expiresIn) 11 | { 12 | Token = token; 13 | ExpiresIn = expiresIn; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/Error.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Core.Dto 4 | { 5 | public sealed class Error 6 | { 7 | public string Code { get; } 8 | public string Description { get; } 9 | 10 | public Error(string code, string description) 11 | { 12 | Code = code; 13 | Description = description; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/GatewayResponses/BaseGatewayResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Web.Api.Core.Dto.GatewayResponses 4 | { 5 | public abstract class BaseGatewayResponse 6 | { 7 | public bool Success { get; } 8 | public IEnumerable Errors { get; } 9 | 10 | protected BaseGatewayResponse(bool success=false, IEnumerable errors=null) 11 | { 12 | Success = success; 13 | Errors = errors; 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/GatewayResponses/Repositories/CreateEntityResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Web.Api.Core.Dto.GatewayResponses.Repositories 4 | { 5 | public sealed class CreateEntityResponse : BaseGatewayResponse 6 | { 7 | public string Id { get; } 8 | public CreateEntityResponse(string id, bool success = false, IEnumerable errors = null) : base(success, errors) 9 | { 10 | Id = id; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/GatewayResponses/Repositories/CreateUserResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Web.Api.Core.Dto.GatewayResponses.Repositories 4 | { 5 | public sealed class CreateUserResponse : BaseGatewayResponse 6 | { 7 | public string Id { get; } 8 | public CreateUserResponse(string id, bool success = false, IEnumerable errors = null) : base(success, errors) 9 | { 10 | Id = id; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/CreateActivityRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Core.Interfaces; 6 | 7 | namespace Web.Api.Core.Dto.UseCaseRequests 8 | { 9 | public class CreateActivityRequest : IUseCaseRequest 10 | { 11 | [Required] 12 | public string Name { get; set; } 13 | [StringLength(500, ErrorMessage = "Description cannot be longer than 70 characters.")] 14 | public string Description { get; set; } 15 | public DateTime DueDate { get; set; } 16 | public Guid ClientId { get; set; } 17 | public DateTime Created { get; set; } 18 | 19 | public CreateActivityRequest(string name, string description) 20 | { 21 | Name = name; 22 | Description = description; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/CreateClientRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Core.Interfaces; 6 | 7 | namespace Web.Api.Core.Dto.UseCaseRequests 8 | { 9 | public class CreateClientRequest : IUseCaseRequest 10 | { 11 | [Required] 12 | public string Name { get; set; } 13 | [StringLength(500, ErrorMessage = "Description cannot be longer than 70 characters.")] 14 | public string Description { get; set; } 15 | public Guid EmployerId { get; set; } 16 | public DateTime Created { get; set; } 17 | 18 | public CreateClientRequest(string name, string description) 19 | { 20 | Name = name; 21 | Description = description; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/CreateEmployerRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Core.Interfaces; 6 | 7 | namespace Web.Api.Core.Dto.UseCaseRequests 8 | { 9 | public class CreateEmployerRequest : IUseCaseRequest 10 | { 11 | [Required] 12 | public string Name { get; set; } 13 | [StringLength(500, ErrorMessage = "Description cannot be longer than 70 characters.")] 14 | public string Description { get; set; } 15 | public DateTime Created { get; set; } 16 | 17 | public CreateEmployerRequest(string name, string description) 18 | { 19 | Name = name; 20 | Description = description; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/ExchangeRefreshTokenRequest.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseResponses; 2 | using Web.Api.Core.Interfaces; 3 | 4 | namespace Web.Api.Core.Dto.UseCaseRequests 5 | { 6 | public class ExchangeRefreshTokenRequest : IUseCaseRequest 7 | { 8 | public string AccessToken { get; } 9 | public string RefreshToken { get; } 10 | public string SigningKey { get; } 11 | 12 | public ExchangeRefreshTokenRequest(string accessToken, string refreshToken, string signingKey) 13 | { 14 | AccessToken = accessToken; 15 | RefreshToken = refreshToken; 16 | SigningKey = signingKey; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/LoginRequest.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseResponses; 2 | using Web.Api.Core.Interfaces; 3 | 4 | namespace Web.Api.Core.Dto.UseCaseRequests 5 | { 6 | public class LoginRequest : IUseCaseRequest 7 | { 8 | public string UserName { get; } 9 | public string Password { get; } 10 | public string RemoteIpAddress { get; } 11 | 12 | public LoginRequest(string userName, string password, string remoteIpAddress) 13 | { 14 | UserName = userName; 15 | Password = password; 16 | RemoteIpAddress = remoteIpAddress; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseRequests/RegisterUserRequest.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseResponses; 2 | using Web.Api.Core.Interfaces; 3 | 4 | namespace Web.Api.Core.Dto.UseCaseRequests 5 | { 6 | public class RegisterUserRequest : IUseCaseRequest 7 | { 8 | public string FirstName { get; } 9 | public string LastName { get; } 10 | public string Email { get; } 11 | public string UserName { get; } 12 | public string Password { get; } 13 | 14 | public RegisterUserRequest(string firstName, string lastName, string email, string userName, string password) 15 | { 16 | FirstName = firstName; 17 | LastName = lastName; 18 | Email = email; 19 | UserName = userName; 20 | Password = password; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseResponses/ExchangeRefreshTokenResponse.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Interfaces; 2 | 3 | namespace Web.Api.Core.Dto.UseCaseResponses 4 | { 5 | public class ExchangeRefreshTokenResponse : UseCaseResponseMessage 6 | { 7 | public AccessToken AccessToken { get; } 8 | public string RefreshToken { get; } 9 | 10 | public ExchangeRefreshTokenResponse(bool success = false, string message = null) : base(success, message) 11 | { 12 | } 13 | 14 | public ExchangeRefreshTokenResponse(AccessToken accessToken, string refreshToken, bool success = false, string message = null) : base(success, message) 15 | { 16 | AccessToken = accessToken; 17 | RefreshToken = refreshToken; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseResponses/LoginResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Web.Api.Core.Interfaces; 3 | 4 | namespace Web.Api.Core.Dto.UseCaseResponses 5 | { 6 | public class LoginResponse : UseCaseResponseMessage 7 | { 8 | public AccessToken AccessToken { get; } 9 | public string RefreshToken { get; } 10 | public IEnumerable Errors { get; } 11 | 12 | public LoginResponse(IEnumerable errors, bool success = false, string message = null) : base(success, message) 13 | { 14 | Errors = errors; 15 | } 16 | 17 | public LoginResponse(AccessToken accessToken, string refreshToken, bool success = false, string message = null) : base(success, message) 18 | { 19 | AccessToken = accessToken; 20 | RefreshToken = refreshToken; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Dto/UseCaseResponses/RegisterUserResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Web.Api.Core.Interfaces; 3 | 4 | namespace Web.Api.Core.Dto.UseCaseResponses 5 | { 6 | public class RegisterUserResponse : UseCaseResponseMessage 7 | { 8 | public string Id { get; } 9 | public IEnumerable Errors { get; } 10 | 11 | public RegisterUserResponse(IEnumerable errors, bool success=false, string message=null) : base(success, message) 12 | { 13 | Errors = errors; 14 | } 15 | 16 | public RegisterUserResponse(string id, bool success = false, string message = null) : base(success, message) 17 | { 18 | Id = id; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IActivitiesRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Domain.Entities; 4 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 5 | 6 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 7 | { 8 | public interface IActivitiesRepository : IRepository 9 | { 10 | Task Create(Activity activity); 11 | Task FindByName(string name); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IClientsRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Domain.Entities; 4 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 5 | 6 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 7 | { 8 | public interface IClientsRepository : IRepository 9 | { 10 | Task Create(Client client); 11 | Task FindByName(string name); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IEmployeesRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Domain.Entities; 4 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 5 | 6 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 7 | { 8 | public interface IEmployeesRepository : IRepository 9 | { 10 | Task Create(string firstName, string lastName); 11 | Task FindByName(string name); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IEmployersRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Domain.Entities; 4 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 5 | 6 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 7 | { 8 | public interface IEmployersRepository : IRepository 9 | { 10 | Task Create(Employer employer); 11 | Task FindByName(string name); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Web.Api.Core.Shared; 5 | 6 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 7 | { 8 | public interface IRepository where T : BaseEntity 9 | { 10 | Task GetById(Guid id); 11 | Task> ListAll(); 12 | Task GetSingleBySpec(ISpecification spec); 13 | Task> List(ISpecification spec); 14 | Task Add(T entity); 15 | Task Update(T entity); 16 | Task Delete(T entity); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/ISpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 6 | { 7 | public interface ISpecification 8 | { 9 | Expression> Criteria { get; } 10 | List>> Includes { get; } 11 | List IncludeStrings { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/ISupervisorsRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Domain.Entities; 4 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 5 | 6 | 7 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 8 | { 9 | public interface ISupervisorsRepository : IRepository 10 | { 11 | Task Create(string firstName, string lastName); 12 | Task FindByName(string name); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Gateways/Repositories/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Web.Api.Core.Domain.Entities; 3 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 4 | 5 | namespace Web.Api.Core.Interfaces.Gateways.Repositories 6 | { 7 | public interface IUserRepository : IRepository 8 | { 9 | Task Create(string firstName, string lastName, string email, string userName, string password); 10 | Task FindByName(string userName); 11 | Task CheckPassword(User user, string password); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/IOutputPort.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Core.Interfaces 4 | { 5 | public interface IOutputPort 6 | { 7 | void Handle(TUseCaseResponse response); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/IUseCaseRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Core.Interfaces 4 | { 5 | public interface IUseCaseRequest { } 6 | } 7 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/IUseCaseRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Web.Api.Core.Interfaces 4 | { 5 | public interface IUseCaseRequestHandler 6 | where TUseCaseRequest : IUseCaseRequest 7 | { 8 | Task Handle(TUseCaseRequest message, IOutputPort outputPort); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/IActivityService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Web.Api.Core.Domain.Entities; 5 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 6 | using Web.Api.Core.Dto.UseCaseRequests; 7 | 8 | namespace Web.Api.Core.Interfaces.Services 9 | { 10 | public interface IActivityService 11 | { 12 | Task> ListAll(); 13 | Task GetById(Guid id); 14 | Task Create(CreateActivityRequest employer); 15 | Task Delete(Activity id); 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/IClientService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Web.Api.Core.Domain.Entities; 5 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 6 | using Web.Api.Core.Dto.UseCaseRequests; 7 | 8 | namespace Web.Api.Core.Interfaces.Services 9 | { 10 | public interface IClientService 11 | { 12 | Task> ListAll(); 13 | Task GetById(Guid id); 14 | Task Create(CreateClientRequest client); 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/IEmployerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Web.Api.Core.Domain.Entities; 5 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 6 | using Web.Api.Core.Dto.UseCaseRequests; 7 | 8 | namespace Web.Api.Core.Interfaces.Services 9 | { 10 | public interface IEmployerService 11 | { 12 | Task> ListAll(); 13 | Task GetById(Guid id); 14 | Task Create(CreateEmployerRequest employer); 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/IJwtFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Web.Api.Core.Dto; 3 | 4 | namespace Web.Api.Core.Interfaces.Services 5 | { 6 | public interface IJwtFactory 7 | { 8 | Task GenerateEncodedToken(string id, string userName); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/IJwtTokenValidator.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Security.Claims; 3 | 4 | namespace Web.Api.Core.Interfaces.Services 5 | { 6 | public interface IJwtTokenValidator 7 | { 8 | ClaimsPrincipal GetPrincipalFromToken(string token, string signingKey); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/ILogger.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Core.Interfaces.Services 4 | { 5 | public interface ILogger 6 | { 7 | void LogInfo(string message); 8 | void LogWarn(string message); 9 | void LogDebug(string message); 10 | void LogError(string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/Services/ITokenFactory.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Web.Api.Core.Interfaces.Services 3 | { 4 | public interface ITokenFactory 5 | { 6 | string GenerateToken(int size= 32); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/UseCaseResponseMessage.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Web.Api.Core.Interfaces 3 | { 4 | public abstract class UseCaseResponseMessage 5 | { 6 | public bool Success { get; } 7 | public string Message { get; } 8 | 9 | protected UseCaseResponseMessage(bool success = false, string message = null) 10 | { 11 | Success = success; 12 | Message = message; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/UseCases/IExchangeRefreshTokenUseCase.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseRequests; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | 4 | namespace Web.Api.Core.Interfaces.UseCases 5 | { 6 | public interface IExchangeRefreshTokenUseCase : IUseCaseRequestHandler 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/UseCases/ILoginUseCase.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseRequests; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | 4 | namespace Web.Api.Core.Interfaces.UseCases 5 | { 6 | public interface ILoginUseCase : IUseCaseRequestHandler 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Interfaces/UseCases/IRegisterUserUseCase.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto.UseCaseRequests; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | 4 | namespace Web.Api.Core.Interfaces.UseCases 5 | { 6 | public interface IRegisterUserUseCase : IUseCaseRequestHandler 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Shared/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | 6 | namespace Web.Api.Core.Shared 7 | { 8 | public abstract class BaseEntity 9 | { 10 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | [Key] 12 | public Guid Id { get; set; } 13 | public DateTime Created { get; set; } 14 | public DateTime Modified { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Specifications/BaseSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using Web.Api.Core.Interfaces.Gateways.Repositories; 5 | 6 | namespace Web.Api.Core.Specifications 7 | { 8 | public abstract class BaseSpecification : ISpecification 9 | { 10 | protected BaseSpecification(Expression> criteria) 11 | { 12 | Criteria = criteria; 13 | } 14 | public Expression> Criteria { get; } 15 | public List>> Includes { get; } = new List>>(); 16 | public List IncludeStrings { get; } = new List(); 17 | 18 | protected virtual void AddInclude(Expression> includeExpression) 19 | { 20 | Includes.Add(includeExpression); 21 | } 22 | protected virtual void AddInclude(string includeString) 23 | { 24 | IncludeStrings.Add(includeString); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Specifications/UserSpecification.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Domain.Entities; 2 | 3 | namespace Web.Api.Core.Specifications 4 | { 5 | public sealed class UserSpecification : BaseSpecification 6 | { 7 | public UserSpecification(string identityId) : base(u => u.IdentityId==identityId) 8 | { 9 | AddInclude(u => u.RefreshTokens); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web.Api.Core/UseCases/RegisterUserUseCase.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Web.Api.Core.Dto.UseCaseRequests; 4 | using Web.Api.Core.Dto.UseCaseResponses; 5 | using Web.Api.Core.Interfaces; 6 | using Web.Api.Core.Interfaces.Gateways.Repositories; 7 | using Web.Api.Core.Interfaces.UseCases; 8 | 9 | namespace Web.Api.Core.UseCases 10 | { 11 | public sealed class RegisterUserUseCase : IRegisterUserUseCase 12 | { 13 | private readonly IUserRepository _userRepository; 14 | 15 | public RegisterUserUseCase(IUserRepository userRepository) 16 | { 17 | _userRepository = userRepository; 18 | } 19 | 20 | public async Task Handle(RegisterUserRequest message, IOutputPort outputPort) 21 | { 22 | var response = await _userRepository.Create(message.FirstName, message.LastName,message.Email, message.UserName, message.Password); 23 | outputPort.Handle(response.Success ? new RegisterUserResponse(response.Id, true) : new RegisterUserResponse(response.Errors.Select(e => e.Description))); 24 | return response.Success; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Web.Api.Core/Web.Api.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("Web.Api.Infrastructure.UnitTests")] 3 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Auth/JwtTokenHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Security.Claims; 4 | using Microsoft.IdentityModel.Tokens; 5 | using Web.Api.Core.Interfaces.Services; 6 | using Web.Api.Infrastructure.Interfaces; 7 | 8 | namespace Web.Api.Infrastructure.Auth 9 | { 10 | internal sealed class JwtTokenHandler : IJwtTokenHandler 11 | { 12 | private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; 13 | private readonly ILogger _logger; 14 | 15 | internal JwtTokenHandler(ILogger logger) 16 | { 17 | if (_jwtSecurityTokenHandler == null) 18 | _jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); 19 | 20 | _logger = logger; 21 | } 22 | 23 | public string WriteToken(JwtSecurityToken jwt) 24 | { 25 | return _jwtSecurityTokenHandler.WriteToken(jwt); 26 | } 27 | 28 | public ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters) 29 | { 30 | try 31 | { 32 | var principal = _jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken); 33 | 34 | if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) 35 | throw new SecurityTokenException("Invalid token"); 36 | 37 | return principal; 38 | } 39 | catch (Exception e) 40 | { 41 | _logger.LogError($"Token validation failed: {e.Message}"); 42 | return null; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Auth/JwtTokenValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text; 3 | using Microsoft.IdentityModel.Tokens; 4 | using Web.Api.Core.Interfaces.Services; 5 | using Web.Api.Infrastructure.Interfaces; 6 | 7 | namespace Web.Api.Infrastructure.Auth 8 | { 9 | internal sealed class JwtTokenValidator : IJwtTokenValidator 10 | { 11 | private readonly IJwtTokenHandler _jwtTokenHandler; 12 | 13 | internal JwtTokenValidator(IJwtTokenHandler jwtTokenHandler) 14 | { 15 | _jwtTokenHandler = jwtTokenHandler; 16 | } 17 | 18 | public ClaimsPrincipal GetPrincipalFromToken(string token, string signingKey) 19 | { 20 | return _jwtTokenHandler.ValidateToken(token, new TokenValidationParameters 21 | { 22 | ValidateAudience = false, 23 | ValidateIssuer = false, 24 | ValidateIssuerSigningKey = true, 25 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), 26 | ValidateLifetime = false // we check expired tokens here 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Auth/TokenFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using Web.Api.Core.Interfaces.Services; 4 | 5 | 6 | namespace Web.Api.Infrastructure.Auth 7 | { 8 | internal sealed class TokenFactory : ITokenFactory 9 | { 10 | public string GenerateToken(int size=32) 11 | { 12 | var randomNumber = new byte[size]; 13 | using (var rng = RandomNumberGenerator.Create()) 14 | { 15 | rng.GetBytes(randomNumber); 16 | return Convert.ToBase64String(randomNumber); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/AppDbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Web.Api.Infrastructure.Data.Seeds; 4 | 5 | namespace Web.Api.Infrastructure.Data 6 | { 7 | public static class AppDbContextExtensions 8 | { 9 | public static int EnsureSeedData(this AppDbContext context) 10 | { 11 | var activitiesCount = default(int); 12 | var organizationsCount = default(int); 13 | var clientsCount = default(int); 14 | var employeesCount = default(int); 15 | 16 | // Because each of the following seed method needs to do a save 17 | // (the data they're importing is relational), we need to call 18 | // SaveAsync within each method. 19 | // So let's keep tabs on the counts as they come back 20 | 21 | var dbSeeder = new DbSeeder(context); 22 | if (!context.Employers.Any()) 23 | { 24 | var pathToSeedData = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "EmployerssSeedData.json"); 25 | organizationsCount = dbSeeder.SeedEmployers(pathToSeedData).Result; 26 | } 27 | // if (!context.Activities.Any()) 28 | // { 29 | // activitiesCount = dbSeeder.SeedActivities().Result; 30 | // } 31 | // if (!context.Clients.Any()) 32 | // { 33 | // clientsCount = dbSeeder.SeedClients().Result; 34 | // } 35 | // if (!context.Employees.Any()) 36 | // { 37 | // employeesCount = dbSeeder.SeedEmployees().Result; 38 | // } 39 | 40 | return activitiesCount + organizationsCount + employeesCount + clientsCount; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/AppDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Web.Api.Infrastructure.Shared; 3 | 4 | 5 | namespace Web.Api.Infrastructure.Data 6 | { 7 | public class AppDbContextFactory : DesignTimeDbContextFactoryBase 8 | { 9 | protected override AppDbContext CreateNewInstance(DbContextOptions options) 10 | { 11 | return new AppDbContext(options); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Mapping/Profiles.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Web.Api.Core.Domain.Entities; 3 | using Web.Api.Infrastructure.Identity; 4 | 5 | 6 | namespace Web.Api.Infrastructure.Data.Mapping 7 | { 8 | public class DataProfile : Profile 9 | { 10 | public DataProfile() 11 | { 12 | CreateMap().ConstructUsing(u => new AppUser {UserName = u.UserName, Email = u.Email}).ForMember(au=>au.Id,opt=>opt.Ignore()); 13 | CreateMap().ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)). 14 | ForMember(dest=> dest.PasswordHash, opt=> opt.MapFrom(src=>src.PasswordHash)). 15 | ForAllOtherMembers(opt=>opt.Ignore()); 16 | 17 | 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Repositories/ClientsRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using AutoMapper; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.EntityFrameworkCore; 10 | using Web.Api.Core.Domain.Entities; 11 | using Web.Api.Core.Dto; 12 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 13 | using Web.Api.Core.Interfaces.Gateways.Repositories; 14 | using Web.Api.Core.Specifications; 15 | using Web.Api.Infrastructure.Identity; 16 | 17 | 18 | namespace Web.Api.Infrastructure.Data.Repositories 19 | { 20 | 21 | internal sealed class ClientsRepository : EfRepository, IClientsRepository 22 | { 23 | private readonly IMapper _mapper; 24 | 25 | 26 | public ClientsRepository(IMapper mapper, AppDbContext appDbContext) : base(appDbContext) 27 | { 28 | _mapper = mapper; 29 | } 30 | 31 | public async Task Create(Client client) 32 | { 33 | var newClient = await _appDbContext.Clients.AddAsync(client); 34 | 35 | if (newClient.Entity.Id == null) 36 | { 37 | return new CreateEntityResponse(newClient.Entity.Id.ToString(), false, new List() { new Error("Create_Client", "Could not create Client") }); 38 | } 39 | 40 | await _appDbContext.SaveChangesAsync(); 41 | return new CreateEntityResponse(newClient.Entity.Id.ToString(), true); 42 | } 43 | public async Task FindByName(string name) 44 | { 45 | var item = await _appDbContext.Clients.FindAsync(name); 46 | return item; 47 | } 48 | async Task IRepository.GetById(Guid id) 49 | { 50 | var client = await _appDbContext.Clients.FindAsync(id); 51 | return client; 52 | } 53 | async Task> IRepository.ListAll() 54 | { 55 | var items = await _appDbContext.Clients.ToListAsync(); 56 | return items; 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Repositories/EmployeesRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using AutoMapper; 5 | using Microsoft.EntityFrameworkCore; 6 | using Web.Api.Core.Domain.Entities; 7 | using Web.Api.Core.Dto; 8 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 9 | using Web.Api.Core.Interfaces.Gateways.Repositories; 10 | 11 | namespace Web.Api.Infrastructure.Data.Repositories 12 | { 13 | internal sealed class EmployeesRepository : EfRepository, IEmployeesRepository 14 | { 15 | private readonly IMapper _mapper; 16 | public EmployeesRepository(IMapper mapper, AppDbContext appDbContext) : base(appDbContext) 17 | { 18 | _mapper = mapper; 19 | } 20 | public async Task Create(string firstName, string lastName) 21 | { 22 | var employee = new Employee { FirstName = firstName, LastName = lastName }; 23 | var newActivity = await _appDbContext.Employees.AddAsync(employee); 24 | 25 | if (newActivity.Entity.Id == null) 26 | { 27 | return new CreateEntityResponse(newActivity.Entity.Id.ToString(), false, new List() { new Error("Create_Employee", "Could not create Employee") }); 28 | } 29 | 30 | await _appDbContext.SaveChangesAsync(); 31 | return new CreateEntityResponse(newActivity.Entity.Id.ToString(), true); 32 | } 33 | public async Task FindByName(string name) 34 | { 35 | var item = await _appDbContext.Employees.FindAsync(name); 36 | return item; 37 | } 38 | async Task IRepository.GetById(Guid id) 39 | { 40 | var employee = await _appDbContext.Employees.FindAsync(id); 41 | return employee; 42 | } 43 | async Task> IRepository.ListAll() 44 | { 45 | var items = await _appDbContext.Employees.ToListAsync(); 46 | return items; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Services/ActivityService.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Interfaces.Services; 2 | using Web.Api.Core.Interfaces.Gateways.Repositories; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using Web.Api.Core.Domain.Entities; 6 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 7 | using Web.Api.Core.Dto.UseCaseRequests; 8 | using System; 9 | 10 | namespace Web.Api.Infrastructure.Data.Services 11 | { 12 | public class ActivityService : IActivityService 13 | { 14 | private readonly IActivitiesRepository _activitiesRepo; 15 | 16 | public ActivityService(IActivitiesRepository activitiesRepo) 17 | { 18 | _activitiesRepo = activitiesRepo; 19 | 20 | } 21 | 22 | public async Task> ListAll() 23 | { 24 | return await _activitiesRepo.ListAll(); 25 | } 26 | public async Task GetById(Guid id) 27 | { 28 | return await _activitiesRepo.GetById(id); 29 | } 30 | public async Task Create(CreateActivityRequest activity) 31 | { 32 | var newActivity = new Activity 33 | { 34 | Name = activity.Name, 35 | Description = activity.Description, 36 | DueDate = activity.DueDate, 37 | ClientId = activity.ClientId 38 | }; 39 | 40 | var result = await _activitiesRepo.Create(newActivity); 41 | return result; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Services/ClientService.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Interfaces.Services; 2 | using Web.Api.Core.Interfaces.Gateways.Repositories; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using Web.Api.Core.Domain.Entities; 6 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 7 | using Web.Api.Core.Dto.UseCaseRequests; 8 | using System; 9 | 10 | namespace Web.Api.Infrastructure.Data.Services 11 | { 12 | public class ClientService : IClientService 13 | { 14 | private readonly IClientsRepository _clientsRepo; 15 | public ClientService(IClientsRepository clientsRepo) 16 | { 17 | _clientsRepo = clientsRepo; 18 | } 19 | 20 | public async Task> ListAll() 21 | { 22 | return await _clientsRepo.ListAll(); 23 | } 24 | public async Task GetById(Guid id) 25 | { 26 | return await _clientsRepo.GetById(id); 27 | } 28 | public async Task Create(CreateClientRequest client) 29 | { 30 | var newClient = new Client 31 | { 32 | Name = client.Name, 33 | Description = client.Description, 34 | EmployerId = client.EmployerId 35 | }; 36 | 37 | var result = await _clientsRepo.Create(newClient); 38 | return result; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Data/Services/EmployerService.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Interfaces.Services; 2 | using Web.Api.Core.Interfaces.Gateways.Repositories; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using Web.Api.Core.Domain.Entities; 6 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 7 | using Web.Api.Core.Dto.UseCaseRequests; 8 | using System; 9 | 10 | namespace Web.Api.Infrastructure.Data.Services 11 | { 12 | public class EmployerService : IEmployerService 13 | { 14 | private readonly IEmployersRepository _employersRepo; 15 | public EmployerService(IEmployersRepository employersRepo) 16 | { 17 | _employersRepo = employersRepo; 18 | } 19 | 20 | public async Task> ListAll() 21 | { 22 | return await _employersRepo.ListAll(); 23 | } 24 | public async Task GetById(Guid id) 25 | { 26 | return await _employersRepo.GetById(id); 27 | } 28 | public async Task Create(CreateEmployerRequest employer) 29 | { 30 | var newEmployer = new Employer 31 | { 32 | Name = employer.Name, 33 | Description = employer.Description 34 | }; 35 | 36 | var result = await _employersRepo.Create(newEmployer); 37 | return result; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Helpers/Constants.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Infrastructure.Helpers 4 | { 5 | public static class Constants 6 | { 7 | public static class Strings 8 | { 9 | public static class JwtClaimIdentifiers 10 | { 11 | public const string Rol = "rol", Id = "id"; 12 | } 13 | 14 | public static class JwtClaims 15 | { 16 | public const string ApiAccess = "api_access"; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Identity/AppIdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | 5 | namespace Web.Api.Infrastructure.Identity 6 | { 7 | public class AppIdentityDbContext : IdentityDbContext 8 | { 9 | public AppIdentityDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Identity/AppIdentityDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Web.Api.Infrastructure.Shared; 3 | 4 | namespace Web.Api.Infrastructure.Identity 5 | { 6 | public class AppIdentityDbContextFactory : DesignTimeDbContextFactoryBase 7 | { 8 | protected override AppIdentityDbContext CreateNewInstance(DbContextOptions options) 9 | { 10 | return new AppIdentityDbContext(options); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Identity/AppUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace Web.Api.Infrastructure.Identity 4 | { 5 | public class AppUser : IdentityUser 6 | { 7 | // Add additional profile data for application users by adding properties to this class 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/InfrastructureModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Web.Api.Core.Interfaces.Gateways.Repositories; 3 | using Web.Api.Core.Interfaces.Services; 4 | using Web.Api.Infrastructure.Auth; 5 | using Web.Api.Infrastructure.Data.Repositories; 6 | using Web.Api.Infrastructure.Data.Services; 7 | using Web.Api.Infrastructure.Interfaces; 8 | using Web.Api.Infrastructure.Logging; 9 | using Module = Autofac.Module; 10 | 11 | namespace Web.Api.Infrastructure 12 | { 13 | public class InfrastructureModule : Module 14 | { 15 | protected override void Load(ContainerBuilder builder) 16 | { 17 | builder.RegisterType().As().InstancePerLifetimeScope(); 18 | builder.RegisterType().As().InstancePerLifetimeScope(); 19 | builder.RegisterType().As().InstancePerLifetimeScope(); 20 | builder.RegisterType().As().InstancePerLifetimeScope(); 21 | builder.RegisterType().As().InstancePerLifetimeScope(); 22 | builder.RegisterType().As().InstancePerLifetimeScope(); 23 | builder.RegisterType().As().InstancePerLifetimeScope(); 24 | builder.RegisterType().As().InstancePerLifetimeScope(); 25 | builder.RegisterType().As().InstancePerLifetimeScope(); 26 | builder.RegisterType().As().SingleInstance().FindConstructorsWith(new InternalConstructorFinder()); 27 | builder.RegisterType().As().SingleInstance().FindConstructorsWith(new InternalConstructorFinder()); 28 | builder.RegisterType().As().SingleInstance(); 29 | builder.RegisterType().As().SingleInstance().FindConstructorsWith(new InternalConstructorFinder()); 30 | builder.RegisterType().As().SingleInstance(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Interfaces/IJwtTokenHandler.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Security.Claims; 3 | using Microsoft.IdentityModel.Tokens; 4 | 5 | 6 | namespace Web.Api.Infrastructure.Interfaces 7 | { 8 | public interface IJwtTokenHandler 9 | { 10 | string WriteToken(JwtSecurityToken jwt); 11 | ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/InternalConstructorFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using Autofac.Core.Activators.Reflection; 5 | 6 | namespace Web.Api.Infrastructure 7 | { 8 | public class InternalConstructorFinder : IConstructorFinder 9 | { 10 | public ConstructorInfo[] FindConstructors(Type t) => t.GetTypeInfo().DeclaredConstructors.Where(c => !c.IsPrivate && !c.IsPublic).ToArray(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | 3 | 4 | namespace Web.Api.Infrastructure.Logging 5 | { 6 | public class Logger : Core.Interfaces.Services.ILogger 7 | { 8 | private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); 9 | 10 | public void LogDebug(string message) 11 | { 12 | logger.Debug(message); 13 | } 14 | 15 | public void LogError(string message) 16 | { 17 | logger.Error(message); 18 | } 19 | 20 | public void LogInfo(string message) 21 | { 22 | logger.Info(message); 23 | } 24 | 25 | public void LogWarn(string message) 26 | { 27 | logger.Warn(message); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/Migrations/20181107211824_Relations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace Web.Api.Infrastructure.Migrations 5 | { 6 | public partial class Relations : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.AddColumn( 11 | name: "ClientId", 12 | table: "Activities", 13 | nullable: false, 14 | defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); 15 | } 16 | 17 | protected override void Down(MigrationBuilder migrationBuilder) 18 | { 19 | migrationBuilder.DropColumn( 20 | name: "ClientId", 21 | table: "Activities"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/README.md: -------------------------------------------------------------------------------- 1 | # Database Deletion 2 | 3 | This section will help you if you want to start a brand new database from an existing database. 4 | (you might need to turn SAFE UPDATE off) 5 | 6 | 1. MySql Workbench > Preferences > SQL EDITOR > (bottom of settings) 7 | 2. turn the SAFE UPDATE off 8 | 3. then log out of your connection and reconnect 9 | 10 | ## Steps to start new 11 | 12 | 1. Delete all data from tables 13 | 14 | ``` 15 | SELECT concat('DELETE FROM ',table_schema,'.',table_name,';') 16 | FROM information_schema.table_constraints 17 | WHERE table_schema='BovsiTime'; 18 | // This MySQL script will create a DELETE FROM script for each table in your database. 19 | ``` 20 | 21 | Copy the output of this and run it. You might need this line-by-line. 22 | 23 | 2. Delete all Foreign Keys from your tables 24 | 25 | ``` 26 | SELECT concat('alter table ',table_schema,'.',table_name,' DROP FOREIGN KEY ',constraint_name,';') 27 | FROM information_schema.table_constraints 28 | WHERE constraint_type='FOREIGN KEY' 29 | AND table_schema='BovsiTime'; 30 | // This MySql script will create na ALTER TABLE _ DROP FOREIGN KEY script for each table in your database. 31 | ``` 32 | 33 | Copy the output of this and run it. You might need this line-by-line. 34 | 35 | # Database Migrations 36 | 37 | If you are having issues reverting migrations. Try using: 38 | 39 | ``` 40 | dotnet ef database update 0 -c AppDbContext 41 | dotnet ef migrations remove -c AppDbContext 42 | 43 | dotnet ef database update 0 -c AppIdentityDbContext 44 | dotnet ef migrations remove -c AppIdentityDbContext 45 | ``` 46 | 47 | If that is the 1st migration (as the name implies) then you can use `dotnet ef database update 0` to revert(unapply) all migrations from the database. You should then be able to run `dotnet ef migrations remove`. 48 | 49 | ## Initial Migrations 50 | 51 | ``` 52 | dotnet ef migrations add Initial -c AppDbContext 53 | dotnet ef migrations add Initial -c AppIdentityDbContext 54 | 55 | dotnet ef database update -c AppDbContext 56 | dotnet ef database update -c AppIdentityDbContext 57 | ``` 58 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/appsettings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Web.Api.Infrastructure { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.8.0.0")] 16 | internal sealed partial class appsettings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static appsettings defaultInstance = ((appsettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new appsettings()))); 19 | 20 | public static appsettings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web.Api.Infrastructure/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // ! Used only as an example template. 3 | // ! Never store any ACTUAL settings in here. 4 | // ! This file will be checked into source control 5 | // ! and be used by developers to setup their own appsettings.{env.EnvrionmentName}.json file 6 | "ConnectionStrings": { 7 | "Default": "server=localhost;port=3306;database=TestDb;user=testuser;password=bestpassword" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api/Controllers/AccountsController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Web.Api.Core.Dto.UseCaseRequests; 4 | using Web.Api.Core.Interfaces.UseCases; 5 | using Web.Api.Presenters; 6 | 7 | namespace Web.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class AccountsController : ControllerBase 12 | { 13 | private readonly IRegisterUserUseCase _registerUserUseCase; 14 | private readonly RegisterUserPresenter _registerUserPresenter; 15 | 16 | public AccountsController(IRegisterUserUseCase registerUserUseCase, RegisterUserPresenter registerUserPresenter) 17 | { 18 | _registerUserUseCase = registerUserUseCase; 19 | _registerUserPresenter = registerUserPresenter; 20 | } 21 | 22 | // POST api/accounts 23 | [HttpPost] 24 | public async Task Post([FromBody] Models.Request.RegisterUserRequest request) 25 | { 26 | if (!ModelState.IsValid) 27 | { 28 | return BadRequest(ModelState); 29 | } 30 | await _registerUserUseCase.Handle(new RegisterUserRequest(request.FirstName, request.LastName, request.Email, request.UserName, request.Password), _registerUserPresenter); 31 | return _registerUserPresenter.ContentResult; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Web.Api/Controllers/ActivitiesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Web.Api.Core.Domain.Entities; 8 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 9 | using Web.Api.Core.Dto.UseCaseRequests; 10 | using Web.Api.Core.Interfaces.Gateways.Repositories; 11 | using Web.Api.Core.Interfaces.Services; 12 | using Web.Api.Infrastructure.Data; 13 | 14 | namespace Web.Api.Controllers 15 | { 16 | [Authorize(Policy = "ApiUser")] 17 | [Produces("application/json")] 18 | [Route("api/[controller]")] 19 | [ApiController] 20 | public class ActivitiesController : Controller 21 | { 22 | private readonly IActivityService _activityService; 23 | public ActivitiesController(IActivityService activityService) 24 | { 25 | _activityService = activityService; 26 | } 27 | // GET api/activities/{id} 28 | [HttpGet("{id}", Name = "GetActivity")] 29 | public async Task GetActivity(Guid id) 30 | { 31 | var activity = await _activityService.GetById(id); 32 | 33 | return activity; 34 | } 35 | // GET api/activities/ 36 | [HttpGet("", Name = "GetActivities")] 37 | public async Task> GetActivities() 38 | { 39 | var activities = await _activityService.ListAll(); 40 | 41 | return activities; 42 | } 43 | [HttpPost] 44 | public async Task Create([FromBody]CreateActivityRequest activity) 45 | { 46 | var activityId = await _activityService.Create(activity); 47 | return activityId; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Web.Api/Controllers/ClientsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Web.Api.Core.Domain.Entities; 7 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 8 | using Web.Api.Core.Dto.UseCaseRequests; 9 | using Web.Api.Core.Interfaces.Gateways.Repositories; 10 | using Web.Api.Core.Interfaces.Services; 11 | 12 | namespace Web.Api.Controllers 13 | { 14 | [Authorize(Policy = "ApiUser")] 15 | [Produces("application/json")] 16 | [Route("api/[controller]")] 17 | [ApiController] 18 | public class ClientsController : Controller 19 | { 20 | private readonly IClientService _clientService; 21 | 22 | public ClientsController(IClientService clientService) 23 | { 24 | _clientService = clientService; 25 | } 26 | 27 | // GET api/client/{id} 28 | [HttpGet("{id}", Name = "GetClient")] 29 | public async Task GetClient(Guid id) 30 | { 31 | var client = await _clientService.GetById(id); 32 | 33 | return client; 34 | } 35 | // GET api/clients 36 | [HttpGet("", Name = "GetCients")] 37 | public async Task> GetClients() 38 | { 39 | var clients = await _clientService.ListAll(); 40 | 41 | return clients; 42 | } 43 | [HttpPost] 44 | public async Task Create([FromBody]CreateClientRequest client) 45 | { 46 | var clientId = await _clientService.Create(client); 47 | return clientId; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Web.Api/Controllers/EmployeesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Web.Api.Core.Domain.Entities; 8 | using Web.Api.Core.Interfaces.Gateways.Repositories; 9 | using Web.Api.Infrastructure.Data; 10 | 11 | namespace Web.Api.Controllers 12 | { 13 | [Authorize(Policy = "ApiUser")] 14 | [Produces("application/json")] 15 | [Route("api/[controller]")] 16 | [ApiController] 17 | public class EmployeesController : Controller 18 | { 19 | private readonly IEmployeesRepository _employeesRepo; 20 | public EmployeesController(IEmployeesRepository employeesRepo) 21 | { 22 | _employeesRepo = employeesRepo; 23 | } 24 | // GET api/employees/ 25 | [HttpGet] 26 | public async Task> GetEmployees() 27 | { 28 | var employees = await _employeesRepo.ListAll(); 29 | 30 | return employees; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Web.Api/Controllers/EmployersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Web.Api.Core.Domain.Entities; 8 | using Web.Api.Core.Dto.GatewayResponses.Repositories; 9 | using Web.Api.Core.Dto.UseCaseRequests; 10 | using Web.Api.Core.Interfaces.Gateways.Repositories; 11 | using Web.Api.Core.Interfaces.Services; 12 | using Web.Api.Infrastructure.Data; 13 | using Web.Api.Models.Request; 14 | 15 | namespace Web.Api.Controllers 16 | { 17 | [Authorize(Policy = "ApiUser")] 18 | [Produces("application/json")] 19 | [Route("api/[controller]")] 20 | [ApiController] 21 | public class EmployersController : Controller 22 | { 23 | private readonly IEmployerService _employerService; 24 | public EmployersController(IEmployerService employerService) 25 | { 26 | _employerService = employerService; 27 | } 28 | // GET api/employers/{id} 29 | [HttpGet("{id}", Name = "GetEmployer")] 30 | public async Task GetEmployer(Guid id) 31 | { 32 | var employer = await _employerService.GetById(id); 33 | 34 | return employer; 35 | } 36 | // GET api/employers 37 | [HttpGet("", Name = "GetEmployers")] 38 | public async Task> GetEmployers() 39 | { 40 | var employers = await _employerService.ListAll(); 41 | 42 | return employers; 43 | } 44 | [HttpPost] 45 | public async Task Create([FromBody]CreateEmployerRequest employer) 46 | { 47 | var employerId = await _employerService.Create(employer); 48 | return employerId; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Web.Api/Controllers/SupervisorsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Web.Api.Core.Domain.Entities; 8 | using Web.Api.Core.Interfaces.Gateways.Repositories; 9 | using Web.Api.Infrastructure.Data; 10 | 11 | namespace Web.Api.Controllers 12 | { 13 | [Authorize(Policy = "ApiUser")] 14 | [Produces("application/json")] 15 | [Route("api/[controller]")] 16 | [ApiController] 17 | public class SupervisorsController : Controller 18 | { 19 | private readonly ISupervisorsRepository _supervisorsRepo; 20 | public SupervisorsController(ISupervisorsRepository supervisorsRepo) 21 | { 22 | _supervisorsRepo = supervisorsRepo; 23 | } 24 | // GET api/supervisors/ 25 | [HttpGet] 26 | public async Task> GetSupervisors() 27 | { 28 | var supervisors = await _supervisorsRepo.ListAll(); 29 | 30 | return supervisors; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Web.Api/Extensions/ResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Web.Api.Helpers; 3 | 4 | namespace Web.Api.Extensions 5 | { 6 | public static class ResponseExtensions 7 | { 8 | public static void AddApplicationError(this HttpResponse response, string message) 9 | { 10 | response.Headers.Add("Application-Error", Strings.RemoveAllNonPrintableCharacters(message)); 11 | // CORS 12 | response.Headers.Add("access-control-expose-headers", "Application-Error"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web.Api/Helpers/SecretChecker.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | 4 | namespace Web.Api.Helpers 5 | { 6 | public static class SecretChecker 7 | { 8 | public static bool CheckUserSuppliedSecretValue(string userSuppliedValue, string secretValue) 9 | { 10 | return (string.IsNullOrWhiteSpace(userSuppliedValue) || string.IsNullOrWhiteSpace(secretValue) 11 | || !string.Equals(userSuppliedValue, secretValue, StringComparison.InvariantCulture)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Web.Api/Helpers/Strings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Web.Api.Helpers 5 | { 6 | public static class Strings 7 | { 8 | public static string RemoveAllNonPrintableCharacters(string target) 9 | { 10 | return Regex.Replace(target, @"\p{C}+", string.Empty); 11 | } 12 | public static string ToUppercaseFirst(this string s) 13 | { 14 | if (string.IsNullOrEmpty(s)) 15 | { 16 | return string.Empty; 17 | } 18 | return char.ToUpper(s[0]) + s.Substring(1); 19 | } 20 | public static string TrimAndReduce(this string str) 21 | { 22 | return ConvertWhitespacesToSingleSpaces(str).Trim(); 23 | } 24 | 25 | public static string ConvertWhitespacesToSingleSpaces(this string value) 26 | { 27 | return Regex.Replace(value, @"\s+", " "); 28 | } 29 | public static int WordCount(this String str) 30 | { 31 | return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Request/ExchangeRefreshTokenRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Models.Request 4 | { 5 | public class ExchangeRefreshTokenRequest 6 | { 7 | public string AccessToken { get; set; } 8 | public string RefreshToken { get; set; } 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Request/LoginRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Models.Request 4 | { 5 | public class LoginRequest 6 | { 7 | public string UserName { get; set; } 8 | public string Password { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Request/RegisterUserRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Models.Request 4 | { 5 | public class RegisterUserRequest 6 | { 7 | public string FirstName { get; set; } 8 | public string LastName { get; set; } 9 | public string Email { get; set; } 10 | public string UserName { get; set; } 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Response/ExchangeRefreshTokenResponse.cs: -------------------------------------------------------------------------------- 1 | using Web.Api.Core.Dto; 2 | 3 | namespace Web.Api.Models.Response 4 | { 5 | public class ExchangeRefreshTokenResponse 6 | { 7 | public AccessToken AccessToken { get; } 8 | public string RefreshToken { get; } 9 | 10 | public ExchangeRefreshTokenResponse(AccessToken accessToken, string refreshToken) 11 | { 12 | AccessToken = accessToken; 13 | RefreshToken = refreshToken; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Response/LoginResponse.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Web.Api.Core.Dto; 4 | 5 | namespace Web.Api.Models.Response 6 | { 7 | public class LoginResponse 8 | { 9 | public AccessToken AccessToken { get; } 10 | public string RefreshToken { get; } 11 | 12 | public LoginResponse(AccessToken accessToken, string refreshToken) 13 | { 14 | AccessToken = accessToken; 15 | RefreshToken = refreshToken; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Settings/AuthSettings.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Web.Api.Models.Settings 4 | { 5 | public class AuthSettings 6 | { 7 | public string SecretKey { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Validation/ExchangeRefreshTokenRequestValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Web.Api.Models.Request; 3 | 4 | namespace Web.Api.Models.Validation 5 | { 6 | public class ExchangeRefreshTokenRequestValidator : AbstractValidator 7 | { 8 | public ExchangeRefreshTokenRequestValidator() 9 | { 10 | RuleFor(x => x.AccessToken).NotEmpty(); 11 | RuleFor(x => x.RefreshToken).NotEmpty(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Validation/LoginRequestValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Web.Api.Models.Request; 3 | 4 | namespace Web.Api.Models.Validation 5 | { 6 | public class LoginRequestValidator : AbstractValidator 7 | { 8 | public LoginRequestValidator() 9 | { 10 | RuleFor(x => x.UserName).NotEmpty(); 11 | RuleFor(x => x.Password).NotEmpty(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Web.Api/Models/Validation/RegisterUserRequestValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Web.Api.Models.Request; 3 | 4 | namespace Web.Api.Models.Validation 5 | { 6 | public class RegisterUserRequestValidator : AbstractValidator 7 | { 8 | public RegisterUserRequestValidator() 9 | { 10 | RuleFor(x => x.FirstName).Length(2, 30); 11 | RuleFor(x => x.LastName).Length(2, 30); 12 | RuleFor(x => x.Email).EmailAddress(); 13 | RuleFor(x => x.UserName).Length(3, 255); 14 | RuleFor(x => x.Password).Length(6, 15); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web.Api/Presenters/ExchangeRefreshTokenPresenter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | using Web.Api.Core.Interfaces; 4 | using Web.Api.Serialization; 5 | 6 | namespace Web.Api.Presenters 7 | { 8 | public sealed class ExchangeRefreshTokenPresenter : IOutputPort 9 | { 10 | public JsonContentResult ContentResult { get; } 11 | 12 | public ExchangeRefreshTokenPresenter() 13 | { 14 | ContentResult = new JsonContentResult(); 15 | } 16 | 17 | public void Handle(ExchangeRefreshTokenResponse response) 18 | { 19 | ContentResult.StatusCode = (int)(response.Success ? HttpStatusCode.OK : HttpStatusCode.BadRequest); 20 | ContentResult.Content = response.Success ? JsonSerializer.SerializeObject(new Models.Response.ExchangeRefreshTokenResponse(response.AccessToken, response.RefreshToken)) : JsonSerializer.SerializeObject(response.Message); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api/Presenters/JsonContentResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Web.Api.Presenters 4 | { 5 | public sealed class JsonContentResult : ContentResult 6 | { 7 | public JsonContentResult() 8 | { 9 | ContentType = "application/json"; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web.Api/Presenters/LoginPresenter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | using Web.Api.Core.Interfaces; 4 | using Web.Api.Serialization; 5 | 6 | namespace Web.Api.Presenters 7 | { 8 | public sealed class LoginPresenter : IOutputPort 9 | { 10 | public JsonContentResult ContentResult { get; } 11 | 12 | public LoginPresenter() 13 | { 14 | ContentResult = new JsonContentResult(); 15 | } 16 | 17 | public void Handle(LoginResponse response) 18 | { 19 | ContentResult.StatusCode = (int)(response.Success ? HttpStatusCode.OK : HttpStatusCode.Unauthorized); 20 | ContentResult.Content = response.Success ? JsonSerializer.SerializeObject(new Models.Response.LoginResponse(response.AccessToken, response.RefreshToken)) : JsonSerializer.SerializeObject(response.Errors); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api/Presenters/RegisterUserPresenter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Web.Api.Core.Dto.UseCaseResponses; 3 | using Web.Api.Core.Interfaces; 4 | using Web.Api.Serialization; 5 | 6 | namespace Web.Api.Presenters 7 | { 8 | public sealed class RegisterUserPresenter : IOutputPort 9 | { 10 | public JsonContentResult ContentResult { get; } 11 | 12 | public RegisterUserPresenter() 13 | { 14 | ContentResult = new JsonContentResult(); 15 | } 16 | 17 | public void Handle(RegisterUserResponse response) 18 | { 19 | ContentResult.StatusCode = (int)(response.Success ? HttpStatusCode.OK : HttpStatusCode.BadRequest); 20 | ContentResult.Content = JsonSerializer.SerializeObject(response); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api/Serialization/JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | 4 | namespace Web.Api.Serialization 5 | { 6 | public sealed class JsonSerializer 7 | { 8 | private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings 9 | { 10 | ContractResolver = new JsonContractResolver(), 11 | NullValueHandling = NullValueHandling.Ignore 12 | }; 13 | 14 | public static string SerializeObject(object o) 15 | { 16 | return JsonConvert.SerializeObject(o, Formatting.Indented, Settings); 17 | } 18 | 19 | public sealed class JsonContractResolver : CamelCasePropertyNamesContractResolver 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Web.Api/Web.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Web.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // ! Used only as an example template. 3 | // ! Never store any ACTUAL settings in here. 4 | // ! This file will be checked into source control 5 | // ! and be used by developers to setup their own appsettings.{env.EnvrionmentName}.json file 6 | "Logging": { 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | }, 13 | "AllowedHosts": "*", 14 | "ConnectionStrings": { 15 | "Default": "server=localhost;port=3306;database=TestDB;user=testuser;password=bestpassword" 16 | }, 17 | "JwtIssuerOptions": { 18 | "Issuer": "webApi", 19 | "Audience": "http://localhost:5002/" 20 | }, 21 | "AuthSettings": { 22 | "SecretKey": "iNivDmHsdfs23sqsfhqGbMRdRj1PVkH" 23 | }, 24 | "Database": { 25 | "DeleteKey": "wodi930f2jf20" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Web.Api/nlog.config: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/README.md: -------------------------------------------------------------------------------- 1 | Woo -------------------------------------------------------------------------------- /src/WebServer/AngularApp/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/WebServer/AngularApp/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import {AppPage} from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to AngularApp!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/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 | } -------------------------------------------------------------------------------- /src/WebServer/AngularApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "rebuild": "rm -rf node_modules/ && npm install" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^7.0.1", 16 | "@angular/cdk": "^7.0.2", 17 | "@angular/common": "^7.0.1", 18 | "@angular/compiler": "^7.0.1", 19 | "@angular/core": "^7.0.1", 20 | "@angular/forms": "^7.0.1", 21 | "@angular/http": "^7.0.1", 22 | "@angular/material": "^7.0.2", 23 | "@angular/material-moment-adapter": "^7.0.2", 24 | "@angular/platform-browser": "^7.0.1", 25 | "@angular/platform-browser-dynamic": "^7.0.1", 26 | "@angular/router": "^7.0.1", 27 | "core-js": "^2.5.4", 28 | "rxjs": "~6.3.3", 29 | "zone.js": "~0.8.26" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "~0.10.0", 33 | "@angular/cli": "~7.0.3", 34 | "@angular/compiler-cli": "^7.0.1", 35 | "@angular/language-service": "^7.0.1", 36 | "@mdi/angular-material": "^2.7.94", 37 | "@types/jasmine": "~2.8.8", 38 | "@types/jasminewd2": "~2.0.3", 39 | "@types/node": "~8.9.4", 40 | "bootstrap": "^4.1.3", 41 | "codelyzer": "~4.3.0", 42 | "font-awesome": "^4.7.0", 43 | "jasmine-core": "~2.99.1", 44 | "jasmine-spec-reporter": "~4.2.1", 45 | "karma": "~3.0.0", 46 | "karma-chrome-launcher": "~2.2.0", 47 | "karma-coverage-istanbul-reporter": "~2.0.1", 48 | "karma-jasmine": "~1.1.2", 49 | "karma-jasmine-html-reporter": "^0.2.2", 50 | "moment": "^2.22.2", 51 | "protractor": "~5.4.0", 52 | "ts-node": "~7.0.0", 53 | "tslint": "~5.11.0", 54 | "typescript": "~3.1.5" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "https://localhost:5002", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "changeOrigin": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .main-outlet { 2 | padding-top: 15px; 3 | padding-bottom: 15px; 4 | min-height: 100%; 5 | height: auto !important; /* cross-browser */ 6 | height: 100%; /* cross-browser */ 7 | } 8 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed, async} from '@angular/core/testing'; 2 | import {AppComponent} from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [AppComponent] 8 | }).compileComponents(); 9 | })); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.debugElement.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'AngularApp'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app.title).toEqual('AngularApp'); 21 | }); 22 | 23 | it('should render title in a h1 tag', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.debugElement.nativeElement; 27 | expect(compiled.querySelector('h1').textContent).toContain( 28 | 'Welcome to AngularApp!' 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {LoggerService} from './core'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'Bovsi Studios Timeclock'; 11 | isDarkTheme: boolean; 12 | constructor(private _logger: LoggerService) { 13 | this._logger.info('Starting App.Component'); 14 | 15 | // get theme setting 16 | const isDarkTheme = localStorage.getItem('isDarkTheme'); 17 | this.isDarkTheme = JSON.parse(isDarkTheme); 18 | } 19 | switchTheme(isDarkTheme: boolean) { 20 | this.isDarkTheme = !isDarkTheme; 21 | this._logger.info(`Dark Theme? ${this.isDarkTheme}`); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {Routes, RouterModule} from '@angular/router'; 3 | 4 | import {PageNotFoundComponent} from './components/error/page-not-found/page-not-found.component'; 5 | import {HomeComponent} from './components/home/home.component'; 6 | import {EmployerListComponent} from './features/employer/employerList/employerList.component'; 7 | import {AuthGuard} from './auth.guard'; 8 | 9 | /*************************************************************** 10 | * Lazy Loading to Eager Loading 11 | * 12 | * 1. Remove the module and NgModule imports in `app.module.ts` 13 | * 14 | * 2. Remove the lazy load route from `app.routing.ts` 15 | * 16 | * 3. Change the module's default route path from '' to 'pathname' 17 | *****************************************************************/ 18 | const appRoutes: Routes = [ 19 | { 20 | path: '', 21 | component: HomeComponent 22 | }, 23 | { 24 | path: '**', 25 | pathMatch: 'full', 26 | component: PageNotFoundComponent 27 | } 28 | ]; 29 | 30 | @NgModule({ 31 | imports: [RouterModule.forRoot(appRoutes, {enableTracing: true})], 32 | exports: [RouterModule] 33 | }) 34 | export class AppRoutingModule {} 35 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/auth.guard.ts: -------------------------------------------------------------------------------- 1 | // auth.guard.ts 2 | import {Injectable} from '@angular/core'; 3 | import { 4 | Router, 5 | CanActivate, 6 | ActivatedRouteSnapshot, 7 | RouterStateSnapshot 8 | } from '@angular/router'; 9 | import {UserService} from './shared/services/user.service'; 10 | 11 | @Injectable() 12 | export class AuthGuard implements CanActivate { 13 | constructor(private _user: UserService, private _router: Router) {} 14 | 15 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 16 | if (this._user.isLoggedIn()) { 17 | return true; 18 | } else { 19 | this._router.navigate(['/account/login'], { 20 | queryParams: { 21 | return: state.url 22 | } 23 | }); 24 | return false; 25 | } 26 | 27 | return true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/error/page-not-found/page-not-found.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/components/error/page-not-found/page-not-found.component.css -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/error/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Inconceivable!

3 |
I do not think this page is where you think it is.
4 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/error/page-not-found/page-not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PageNotFoundComponent } from './page-not-found.component'; 4 | 5 | describe('PageNotFoundComponent', () => { 6 | let component: PageNotFoundComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PageNotFoundComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PageNotFoundComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/error/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-not-found', 5 | templateUrl: './page-not-found.component.html', 6 | styleUrls: ['./page-not-found.component.css'] 7 | }) 8 | export class PageNotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

Bovsi Studios Timeclock app

6 |

This app will help Bovsi Studios keep track of work-things

7 |

8 | Signup with 9 | email 10 | Login with 11 | Username 12 |

13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/components/home/home.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent { 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, Optional, SkipSelf} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {LoggerService} from './logger/logger.service'; 4 | import {ConsoleLoggerService} from './logger/consoleLogger.service'; 5 | import {RouterModule} from '@angular/router'; 6 | import {BrowserModule} from '@angular/platform-browser'; 7 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 8 | 9 | @NgModule({ 10 | imports: [BrowserModule, BrowserAnimationsModule, RouterModule], 11 | exports: [BrowserModule, BrowserAnimationsModule], 12 | declarations: [], 13 | providers: [{provide: LoggerService, useClass: ConsoleLoggerService}] 14 | }) 15 | export class CoreModule { 16 | constructor( 17 | @Optional() 18 | @SkipSelf() 19 | parentModule: CoreModule 20 | ) { 21 | if (parentModule) { 22 | throw new Error( 23 | `CoreModule has already been loaded. Import Core modules in the AppModule only.` 24 | ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/http/abstractRestService.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient, HttpParams, HttpHeaders} from '@angular/common/http'; 3 | import {Entity} from '../../models/entity.model'; 4 | import {Observable} from 'rxjs'; 5 | import {map} from 'rxjs/operators'; 6 | 7 | @Injectable() 8 | export abstract class AbstractRestService { 9 | _headers: any; 10 | constructor(private _httpClient: HttpClient, private _baseUrl: string) { 11 | this._headers = { 12 | headers: new HttpHeaders({ 13 | 'Content-Type': 'application/json' 14 | }) 15 | }; 16 | } 17 | 18 | public create(_item: T): Observable { 19 | const body = JSON.stringify(_item); 20 | return this._httpClient.post(`${this._baseUrl}`, body) as Observable; 21 | } 22 | public getAll(): Observable { 23 | return this._httpClient.get(this._baseUrl) as Observable; 24 | } 25 | public deleteOne(): Observable { 26 | return this._httpClient.delete(this._baseUrl) as Observable; 27 | } 28 | public getOne(id: string): Observable { 29 | return this._httpClient.get(`${this._baseUrl}/${id}`) as Observable; 30 | } 31 | // public update(_item: T): Observable { 32 | // const o: any = JSON.stringify({item: _item}); 33 | 34 | // return this._httpClient 35 | // .put(`${this._baseUrl}/${this.endpoint}/${o.id}`, o) 36 | // .pipe(map(this.extractData)); 37 | // } 38 | 39 | // delete(id: number) { 40 | // return this._httpClient.delete(`${this._baseUrl}/${this.endpoint}/${id}`); 41 | // } 42 | 43 | private convertData(data: any): T[] { 44 | return data.map(item => item); 45 | } 46 | private extractData(res: Response) { 47 | const body = res; 48 | return body || {}; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './config'; 2 | export * from './http/abstractRestService.service'; 3 | export * from './logger/logger.service'; 4 | export * from './logger/consoleLogger.service'; 5 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/logger/consoleLogger.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {environment} from '../../../environments/environment'; 3 | 4 | import {Logger} from './logger.service'; 5 | 6 | export let isDebugMode = environment.isDebugMode; 7 | 8 | const noop = (): any => undefined; 9 | 10 | @Injectable() 11 | export class ConsoleLoggerService implements Logger { 12 | get clear() { 13 | if (isDebugMode) { 14 | return console.clear.bind(console); 15 | } else { 16 | return noop; 17 | } 18 | } 19 | get info() { 20 | if (isDebugMode) { 21 | return console.info.bind(console); 22 | } else { 23 | return noop; 24 | } 25 | } 26 | 27 | get warn() { 28 | if (isDebugMode) { 29 | return console.warn.bind(console); 30 | } else { 31 | return noop; 32 | } 33 | } 34 | 35 | get error() { 36 | if (isDebugMode) { 37 | return console.error.bind(console); 38 | } else { 39 | return noop; 40 | } 41 | } 42 | 43 | get table() { 44 | if (isDebugMode) { 45 | return console.table.bind(console); 46 | } else { 47 | return noop; 48 | } 49 | } 50 | 51 | invokeConsoleMethod(type: string, args?: any): void { 52 | const logFn: Function = console[type] || console.log || noop; 53 | logFn.apply(console, [args]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/logger/logger.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | export abstract class Logger { 4 | clear: any; 5 | info: any; 6 | warn: any; 7 | error: any; 8 | table: any; 9 | } 10 | 11 | @Injectable() 12 | export class LoggerService implements Logger { 13 | clear: any; 14 | info: any; 15 | warn: any; 16 | error: any; 17 | table: any; 18 | 19 | invokeConsoleMethod(type: string, args?: any): void {} 20 | } 21 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/top-nav-menu/top-nav-menu.component.scss: -------------------------------------------------------------------------------- 1 | .nav-menu-spacer { 2 | flex: 1 1 auto; 3 | } 4 | .logo-image { 5 | max-height: 100%; 6 | max-width: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/top-nav-menu/top-nav-menu.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TopNavMenuComponent } from './top-nav-menu.component'; 4 | 5 | describe('TopNavMenuComponent', () => { 6 | let component: TopNavMenuComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TopNavMenuComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TopNavMenuComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/core/top-nav-menu/top-nav-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | ViewEncapsulation, 5 | EventEmitter, 6 | Output 7 | } from '@angular/core'; 8 | import {UserService} from '../../shared/services/user.service'; 9 | import {LoggerService} from '../logger/logger.service'; 10 | 11 | @Component({ 12 | selector: 'app-top-nav-menu', 13 | templateUrl: './top-nav-menu.component.html', 14 | styleUrls: ['./top-nav-menu.component.scss'] 15 | }) 16 | export class TopNavMenuComponent implements OnInit { 17 | isLoggedIn: boolean; 18 | // isDarkTheme: boolean; 19 | @Output() 20 | changeThemeEvent = new EventEmitter(); 21 | isDarkTheme: boolean; 22 | constructor( 23 | private _userService: UserService, 24 | private _logger: LoggerService 25 | ) { 26 | this.isLoggedIn = this._userService.isLoggedIn(); 27 | } 28 | 29 | ngOnInit() {} 30 | changeTheme(): void { 31 | localStorage.setItem('isDarkTheme', JSON.stringify(this.isDarkTheme)); 32 | 33 | if (this.isDarkTheme) { 34 | this.isDarkTheme = false; 35 | } else { 36 | this.isDarkTheme = true; 37 | } 38 | 39 | this.changeThemeEvent.emit(this.isDarkTheme); 40 | } 41 | logout() { 42 | this._userService.logout(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/directives/emailValidator.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, forwardRef} from '@angular/core'; 2 | import {NG_VALIDATORS, FormControl} from '@angular/forms'; 3 | 4 | function validateEmailFactory() { 5 | return (c: FormControl) => { 6 | const EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 7 | 8 | return EMAIL_REGEXP.test(c.value) 9 | ? null 10 | : { 11 | validateEmail: { 12 | valid: false 13 | } 14 | }; 15 | }; 16 | } 17 | 18 | @Directive({ 19 | // tslint:disable-next-line:directive-selector 20 | selector: '[validateEmail][ngModel],[validateEmail][formControl]', 21 | providers: [ 22 | { 23 | provide: NG_VALIDATORS, 24 | useExisting: forwardRef(() => EmailValidatorDirective), 25 | multi: true 26 | } 27 | ] 28 | }) 29 | export class EmailValidatorDirective { 30 | validator: Function; 31 | 32 | constructor() { 33 | this.validator = validateEmailFactory(); 34 | } 35 | 36 | validate(c: FormControl) { 37 | return this.validator(c); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/directives/focus.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, ElementRef, Renderer, OnInit} from '@angular/core'; 2 | 3 | // tslint:disable-next-line:directive-selector 4 | @Directive({selector: '[tmFocus]'}) 5 | export class MyFocusDirective implements OnInit { 6 | constructor(private el: ElementRef, private renderer: Renderer) { 7 | // focus won't work at construction time - too early 8 | } 9 | 10 | ngOnInit() { 11 | this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {FormsModule} from '@angular/forms'; 4 | 5 | import {LoginFormComponent} from './loginForm/loginForm.component'; 6 | import {RegistrationFormComponent} from './registrationForm/registrationForm.component'; 7 | import {UserService} from '../../shared/services/user.service'; 8 | import {AccountRoutingModule} from './account.routing'; 9 | import {SharedModule} from '../../shared/shared.module'; 10 | import {EmailValidatorDirective} from '../../directives/emailValidator.directive'; 11 | 12 | // import { FacebookLoginComponent } from './facebook-login/facebook-login.component'; 13 | 14 | @NgModule({ 15 | imports: [CommonModule, FormsModule, AccountRoutingModule, SharedModule], 16 | declarations: [ 17 | RegistrationFormComponent, 18 | EmailValidatorDirective, 19 | LoginFormComponent 20 | ], 21 | providers: [UserService] 22 | }) 23 | export class AccountModule {} 24 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/account.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {LoginFormComponent} from './loginForm/loginForm.component'; 4 | import {RegistrationFormComponent} from './registrationForm/registrationForm.component'; 5 | 6 | const accountRoutes: Routes = [ 7 | {path: 'account/register', component: RegistrationFormComponent}, 8 | {path: 'account/login', component: LoginFormComponent} 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(accountRoutes)], 13 | exports: [RouterModule] 14 | }) 15 | export class AccountRoutingModule {} 16 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/loginForm/loginForm.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |

Login

7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 |
15 | 16 | 18 |
19 |
20 | 21 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 | 33 | 34 |
35 |
36 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/loginForm/loginForm.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/account/loginForm/loginForm.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/loginForm/loginForm.component.ts: -------------------------------------------------------------------------------- 1 | import {Subscription} from 'rxjs'; 2 | import {Component, OnInit, OnDestroy} from '@angular/core'; 3 | import {Router, ActivatedRoute} from '@angular/router'; 4 | import {Credentials} from '../../../shared/models/credentials.interface'; 5 | import {UserService} from '../../../shared/services/user.service'; 6 | 7 | @Component({ 8 | selector: 'app-login-form', 9 | templateUrl: './loginForm.component.html', 10 | styleUrls: ['./loginForm.component.scss'] 11 | }) 12 | export class LoginFormComponent implements OnInit, OnDestroy { 13 | private subscription: Subscription; 14 | 15 | brandNew: boolean; 16 | errors: string; 17 | isRequesting: boolean; 18 | submitted: boolean = false; 19 | credentials: Credentials = {username: '', password: ''}; 20 | returnUrl: string; 21 | 22 | constructor( 23 | private userService: UserService, 24 | private router: Router, 25 | private activatedRoute: ActivatedRoute 26 | ) {} 27 | 28 | ngOnInit() { 29 | // Get the query params 30 | this.activatedRoute.queryParams.subscribe( 31 | params => (this.returnUrl = params['return'] || '/dashboard') 32 | ); 33 | // subscribe to router event 34 | this.subscription = this.activatedRoute.queryParams.subscribe( 35 | (param: any) => { 36 | this.brandNew = param['brandNew']; 37 | this.credentials.username = param['username']; 38 | } 39 | ); 40 | } 41 | 42 | ngOnDestroy() { 43 | // prevent memory leak by unsubscribing 44 | this.subscription.unsubscribe(); 45 | } 46 | 47 | login({value, valid}: {value: Credentials; valid: boolean}) { 48 | this.submitted = true; 49 | this.isRequesting = true; 50 | this.errors = ''; 51 | if (valid) { 52 | this.userService.login(value.username, value.password).subscribe( 53 | result => { 54 | if (result) { 55 | this.router.navigateByUrl(this.returnUrl); 56 | } 57 | }, 58 | error => (this.errors = error), 59 | () => (this.isRequesting = false) 60 | ); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/registrationForm/registrationForm.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/account/registrationForm/registrationForm.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/account/registrationForm/registrationForm.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Router} from '@angular/router'; 3 | import {UserRegistration} from '../../../shared/models/userRegistration.interface'; 4 | import {UserService} from '../../../shared/services/user.service'; 5 | 6 | @Component({ 7 | selector: 'app-registration-form', 8 | templateUrl: './registrationForm.component.html', 9 | styleUrls: ['./registrationForm.component.scss'] 10 | }) 11 | export class RegistrationFormComponent implements OnInit { 12 | errors: string; 13 | isRequesting: boolean; 14 | submitted = false; 15 | 16 | constructor(private userService: UserService, private router: Router) {} 17 | 18 | ngOnInit() {} 19 | 20 | registerUser({value, valid}: {value: UserRegistration; valid: boolean}) { 21 | this.submitted = true; 22 | this.isRequesting = true; 23 | this.errors = ''; 24 | if (valid) { 25 | this.userService 26 | .register( 27 | value.email, 28 | value.password, 29 | value.firstName, 30 | value.lastName, 31 | value.userName 32 | ) 33 | .subscribe( 34 | result => { 35 | if (result) { 36 | this.router.navigate(['/dashboard'], { 37 | queryParams: {brandNew: true, email: value.email} 38 | }); 39 | } 40 | }, 41 | errors => (this.errors = errors), 42 | () => (this.isRequesting = false) 43 | ); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/accountHome/accountHome.component.html: -------------------------------------------------------------------------------- 1 |

2 | settings works! 3 |

-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/accountHome/accountHome.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/dashboard/accountHome/accountHome.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/accountHome/accountHome.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-account-home', 5 | templateUrl: './accountHome.component.html', 6 | styleUrls: ['./accountHome.component.scss'] 7 | }) 8 | export class AccountHomeComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {FormsModule} from '@angular/forms'; 4 | 5 | import {DashboardRoutingModule} from './dashboard.routing'; 6 | import {RootComponent} from './root/root.component'; 7 | import {HomeComponent} from './home/home.component'; 8 | 9 | import {SharedModule} from '../../shared/shared.module'; 10 | import {AuthGuard} from '../../auth.guard'; 11 | import {AccountHomeComponent} from './accountHome/accountHome.component'; 12 | 13 | @NgModule({ 14 | imports: [CommonModule, FormsModule, DashboardRoutingModule, SharedModule], 15 | declarations: [RootComponent, HomeComponent, AccountHomeComponent], 16 | exports: [], 17 | providers: [AuthGuard] 18 | }) 19 | export class DashboardModule {} 20 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/dashboard.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | 4 | import {RootComponent} from './root/root.component'; 5 | import {HomeComponent} from './home/home.component'; 6 | import {AuthGuard} from '../../auth.guard'; 7 | import {AccountHomeComponent} from './accountHome/accountHome.component'; 8 | 9 | const dashboardRoutes: Routes = [ 10 | { 11 | path: 'dashboard', 12 | component: RootComponent, 13 | canActivate: [AuthGuard], 14 | 15 | children: [ 16 | {path: '', component: HomeComponent}, 17 | {path: 'home', component: HomeComponent}, 18 | {path: 'account', component: AccountHomeComponent} 19 | ] 20 | } 21 | ]; 22 | @NgModule({ 23 | imports: [RouterModule.forChild(dashboardRoutes)], 24 | exports: [RouterModule] 25 | }) 26 | export class DashboardRoutingModule {} 27 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/dashboard/home/home.component.css -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/home/home.component.html: -------------------------------------------------------------------------------- 1 |

2 | home works! 3 |

4 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/root/root.component.html: -------------------------------------------------------------------------------- 1 |
2 | 12 |
13 | 14 |
15 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/root/root.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/dashboard/root/root.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/dashboard/root/root.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './root.component.html', 6 | styleUrls: ['./root.component.scss'] 7 | }) 8 | export class RootComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activity.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {ActivityUpsertComponent} from './activityUpsert/activityUpsert.component'; 3 | import {CommonModule} from '@angular/common'; 4 | import {ReactiveFormsModule} from '@angular/forms'; 5 | import {SharedModule} from 'src/app/shared/shared.module'; 6 | import {ActivityRoutingModule} from './activity.routing'; 7 | import {ActivityListComponent} from './activityList/activityList.component'; 8 | import {ActivityService} from './services/activity.service'; 9 | import {ActivityComponent} from './root/activity.component'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | ReactiveFormsModule, 15 | SharedModule, 16 | ActivityRoutingModule 17 | ], 18 | exports: [], 19 | declarations: [ActivityComponent, ActivityUpsertComponent], 20 | providers: [ActivityService] 21 | }) 22 | export class ActivityModule {} 23 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activity.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {ActivityUpsertComponent} from './activityUpsert/activityUpsert.component'; 4 | import {ActivityListComponent} from './activityList/activityList.component'; 5 | import {AuthGuard} from 'src/app/auth.guard'; 6 | import {ActivityComponent} from './root/activity.component'; 7 | 8 | const activityRoutes: Routes = [ 9 | { 10 | path: 'employer/:employerId/client/:clientId/activity/:activityId', 11 | component: ActivityComponent, 12 | canActivate: [AuthGuard] 13 | }, 14 | { 15 | path: 'activities', 16 | component: ActivityListComponent, 17 | canActivate: [AuthGuard] 18 | }, 19 | { 20 | path: 'activity/create', 21 | component: ActivityUpsertComponent, 22 | data: {type: 'create'}, 23 | canActivate: [AuthGuard] 24 | }, 25 | { 26 | path: 'activity/update/:id', 27 | component: ActivityUpsertComponent, 28 | data: {type: 'update'}, 29 | canActivate: [AuthGuard] 30 | } 31 | ]; 32 | 33 | @NgModule({ 34 | imports: [RouterModule.forChild(activityRoutes)], 35 | exports: [RouterModule] 36 | }) 37 | export class ActivityRoutingModule {} 38 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityList/activityList.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityList/activityList.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityList/activityList.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewChild} from '@angular/core'; 2 | import {Activity} from 'src/app/models/activity.model'; 3 | import {MatPaginator, MatTableDataSource} from '@angular/material'; 4 | import {ActivityService} from '../services/activity.service'; 5 | import {NotificationService} from 'src/app/shared/notifications'; 6 | import {LoggerService} from 'src/app/core'; 7 | import {AngularWaitBarrier} from 'blocking-proxy/built/lib/angular_wait_barrier'; 8 | 9 | @Component({ 10 | selector: 'app-activity-list', 11 | templateUrl: 'activityList.component.html' 12 | }) 13 | export class ActivityListComponent implements OnInit { 14 | activities: Activity[] = new Array(); 15 | displayedColumns: string[] = ['name', 'description', 'created', 'actions']; 16 | @ViewChild(MatPaginator) 17 | paginator: MatPaginator; 18 | dataSource: MatTableDataSource; 19 | constructor( 20 | private _logger: LoggerService, 21 | private _notif: NotificationService, 22 | private _activitiesService: ActivityService 23 | ) {} 24 | 25 | async ngOnInit() { 26 | await this.getActivities(); 27 | } 28 | async getActivities() { 29 | await this._activitiesService.getAll().subscribe( 30 | res => { 31 | this._logger.info('Activities: ', res); 32 | this.activities = res; 33 | this.dataSource = new MatTableDataSource(this.activities); 34 | this.dataSource.paginator = this.paginator; 35 | }, 36 | error => { 37 | this._logger.error(`error getting activities`); 38 | this._notif.show(`ERROR: ` + error); 39 | } 40 | ); 41 | } 42 | 43 | applyFilter(filterValue: string) { 44 | this.dataSource.filter = filterValue.trim().toLowerCase(); 45 | 46 | if (this.dataSource.paginator) { 47 | this.dataSource.paginator.firstPage(); 48 | } 49 | } 50 | 51 | deleteActivity(activityId: string) { 52 | this._logger.info('DELETING ACTIVITY', activityId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityUpsert/activityUpsert.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{pageAction}} Activity 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityUpsert/activityUpsert.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/activity/activityUpsert/activityUpsert.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/root/activity.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 8 | ACTIVITY: {{ activity.name }} 9 | 10 | 11 | {{ activity.description }} 12 | 13 | 14 | 15 | 16 | {{ activity.dueDate | date:'fullDate' }} 17 | 18 | Content 2 19 | Content 3 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/root/activity.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/activity/root/activity.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/root/activity.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ActivatedRoute} from '@angular/router'; 3 | import {LoggerService} from 'src/app/core'; 4 | import {NotificationService} from 'src/app/shared/notifications'; 5 | import {Observable} from 'rxjs/internal/Observable'; 6 | import {Activity} from 'src/app/models/activity.model'; 7 | import {ActivityService} from '../services/activity.service'; 8 | 9 | @Component({ 10 | selector: 'app-activity', 11 | templateUrl: './activity.component.html', 12 | styleUrls: ['./activity.component.scss'] 13 | }) 14 | export class ActivityComponent implements OnInit { 15 | activity: Observable; 16 | employerId: string; 17 | clientId: string; 18 | activityId: string; 19 | 20 | constructor( 21 | private route: ActivatedRoute, 22 | private _logger: LoggerService, 23 | private _notif: NotificationService, 24 | private _activityService: ActivityService 25 | ) {} 26 | 27 | async ngOnInit() { 28 | this.employerId = this.route.snapshot.params['employerId']; 29 | this.clientId = this.route.snapshot.params['clientId']; 30 | this.activityId = this.route.snapshot.params['activityId']; 31 | 32 | this.activity = this._activityService.getOne(this.activityId); 33 | } 34 | async getActivity() { 35 | return this._activityService.getOne(this.activityId).subscribe( 36 | res => { 37 | this._logger.info('ACTIVITY: ', res); 38 | // this.employer = res; 39 | }, 40 | error => { 41 | this._logger.error('error getting activity', error); 42 | this._notif.showError(`ERROR: ${error.message}`); 43 | } 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/activity/services/activity.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {Activity} from 'src/app/models/activity.model'; 4 | import {AbstractRestService} from 'src/app/core/http/abstractRestService.service'; 5 | 6 | @Injectable() 7 | export class ActivityService extends AbstractRestService { 8 | constructor(http: HttpClient) { 9 | super(http, '/api/activities'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clients.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {ReactiveFormsModule} from '@angular/forms'; 4 | import {SharedModule} from 'src/app/shared/shared.module'; 5 | import {ClientsRoutingModule} from './clients.routing'; 6 | import {ClientsService} from './services/clients.service'; 7 | import {ClientsListComponent} from './clientsList/clientsList.component'; 8 | import {ClientsUpsertComponent} from './clientsUpsert/clientsUpsert.component'; 9 | import {ClientComponent} from './root/client.component'; 10 | import {ActivityModule} from './activity/activity.module'; 11 | import {ActivityListComponent} from './activity/activityList/activityList.component'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | ReactiveFormsModule, 17 | SharedModule, 18 | ClientsRoutingModule, 19 | ActivityModule 20 | ], 21 | exports: [], 22 | declarations: [ 23 | ActivityListComponent, 24 | ClientComponent, 25 | ClientsUpsertComponent 26 | ], 27 | providers: [ClientsService] 28 | }) 29 | export class ClientsModule {} 30 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clients.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {AuthGuard} from 'src/app/auth.guard'; 4 | import {ClientsUpsertComponent} from './clientsUpsert/clientsUpsert.component'; 5 | import {ClientsListComponent} from './clientsList/clientsList.component'; 6 | import {ClientComponent} from './root/client.component'; 7 | 8 | const clientsRoutes: Routes = [ 9 | { 10 | path: 'employer/:employerId/client/:clientId', 11 | component: ClientComponent, 12 | children: [ 13 | { 14 | path: 'activities', 15 | loadChildren: './activity/activity.module#ActivityModule' 16 | } 17 | ], 18 | canActivate: [AuthGuard] 19 | }, 20 | { 21 | path: 'employer/:employerId/clients', 22 | component: ClientsListComponent, 23 | canActivate: [AuthGuard] 24 | }, 25 | { 26 | path: 'client/create', 27 | component: ClientsUpsertComponent, 28 | data: {type: 'create'}, 29 | canActivate: [AuthGuard] 30 | }, 31 | { 32 | path: 'client/update/:id', 33 | component: ClientsUpsertComponent, 34 | data: {type: 'update'}, 35 | canActivate: [AuthGuard] 36 | } 37 | ]; 38 | 39 | @NgModule({ 40 | imports: [RouterModule.forChild(clientsRoutes)], 41 | exports: [RouterModule] 42 | }) 43 | export class ClientsRoutingModule {} 44 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clientsList/clientsList.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/clientsList/clientsList.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clientsList/clientsList.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewChild} from '@angular/core'; 2 | import {MatPaginator, MatTableDataSource} from '@angular/material'; 3 | import {NotificationService} from 'src/app/shared/notifications'; 4 | import {LoggerService} from 'src/app/core'; 5 | import {Client} from 'src/app/models/client.model'; 6 | import {ClientsService} from 'src/app/features/timeTracker/services/clients.service'; 7 | 8 | @Component({ 9 | selector: 'app-clients-list', 10 | templateUrl: './clientsList.component.html', 11 | styleUrls: ['./clientsList.component.scss'] 12 | }) 13 | export class ClientsListComponent implements OnInit { 14 | employerId: string; 15 | clients: Client[] = new Array(); 16 | displayedColumns: string[] = ['name', 'description', 'created', 'actions']; 17 | @ViewChild(MatPaginator) 18 | paginator: MatPaginator; 19 | dataSource: MatTableDataSource; 20 | constructor( 21 | private _logger: LoggerService, 22 | private _notif: NotificationService, 23 | private _clientService: ClientsService 24 | ) {} 25 | 26 | async ngOnInit() { 27 | await this.getClients(); 28 | } 29 | async getClients() { 30 | await this._clientService.getAll().subscribe( 31 | res => { 32 | this._logger.info('Clients: ', res); 33 | this.clients = res; 34 | this.dataSource = new MatTableDataSource(this.clients); 35 | this.dataSource.paginator = this.paginator; 36 | }, 37 | error => { 38 | this._logger.error(`error getting clients`); 39 | this._notif.show(`ERROR: ` + error); 40 | } 41 | ); 42 | } 43 | applyFilter(filterValue: string) { 44 | this.dataSource.filter = filterValue.trim().toLowerCase(); 45 | 46 | if (this.dataSource.paginator) { 47 | this.dataSource.paginator.firstPage(); 48 | } 49 | } 50 | 51 | delentClient(clientId: string) { 52 | this._logger.info('DELETEING CLIENT', clientId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clientsUpsert/clientsUpsert.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{pageAction}} Client 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/clientsUpsert/clientsUpsert.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/clientsUpsert/clientsUpsert.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/root/client.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | CLIENT: {{ client.name }} 22 | 23 | 24 | {{ client.description }} 25 | 26 | 27 | 28 | Content 1 29 | Content 2 30 | Content 3 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/root/client.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/clients/root/client.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/root/client.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ActivatedRoute} from '@angular/router'; 3 | import {LoggerService} from 'src/app/core'; 4 | import {NotificationService} from 'src/app/shared/notifications'; 5 | import {Observable} from 'rxjs/internal/Observable'; 6 | import {ClientsService} from 'src/app/features/timeTracker/services/clients.service'; 7 | import {Client} from 'src/app/models/client.model'; 8 | 9 | @Component({ 10 | selector: 'app-client', 11 | templateUrl: './client.component.html', 12 | styleUrls: ['./client.component.scss'] 13 | }) 14 | export class ClientComponent implements OnInit { 15 | client: Observable; 16 | employerId: string; 17 | clientId: string; 18 | 19 | constructor( 20 | private route: ActivatedRoute, 21 | private _logger: LoggerService, 22 | private _notif: NotificationService, 23 | private _clientsService: ClientsService 24 | ) {} 25 | 26 | async ngOnInit() { 27 | this.clientId = this.route.snapshot.params['clientId']; 28 | this.employerId = this.route.snapshot.params['employerId']; 29 | 30 | this.client = this._clientsService.getOne(this.clientId); 31 | } 32 | async getClient() { 33 | return this._clientsService.getOne(this.clientId).subscribe( 34 | res => { 35 | this._logger.info('CLIENT: ', res); 36 | // this.employer = res; 37 | }, 38 | error => { 39 | this._logger.error('error getting client', error); 40 | this._notif.showError(`ERROR: ${error.message}`); 41 | } 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/clients/services/clients.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {Client} from 'src/app/models/client.model'; 4 | import {AbstractRestService} from 'src/app/core'; 5 | 6 | @Injectable() 7 | export class ClientsService extends AbstractRestService { 8 | constructor(http: HttpClient) { 9 | super(http, '/api/clients'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employer.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | 3 | import {EmployerService} from './services/employer.service'; 4 | import {EmployerListComponent} from './employerList/employerList.component'; 5 | import {EmployerRoutingModule} from './employer.routing'; 6 | import {CommonModule} from '@angular/common'; 7 | import {SharedModule} from 'src/app/shared/shared.module'; 8 | import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 9 | import {EmployerUpsertComponent} from './employerUpsert/employerUpsert.component'; 10 | import {ClientsModule} from './clients/clients.module'; 11 | import {EmployerComponent} from './root/employer.component'; 12 | import {ClientsListComponent} from './clients/clientsList/clientsList.component'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | ReactiveFormsModule, 18 | FormsModule, 19 | SharedModule, 20 | EmployerRoutingModule, 21 | ClientsModule 22 | ], 23 | exports: [], 24 | declarations: [ 25 | EmployerComponent, 26 | EmployerListComponent, 27 | EmployerUpsertComponent, 28 | ClientsListComponent 29 | ], 30 | providers: [EmployerService] 31 | }) 32 | export class EmployerModule {} 33 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employer.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {EmployerListComponent} from './employerList/employerList.component'; 4 | import {EmployerUpsertComponent} from './employerUpsert/employerUpsert.component'; 5 | import {AuthGuard} from 'src/app/auth.guard'; 6 | import {EmployerComponent} from './root/employer.component'; 7 | 8 | const employerRoutes: Routes = [ 9 | { 10 | path: 'employers', 11 | component: EmployerListComponent, 12 | canActivate: [AuthGuard] 13 | }, 14 | { 15 | path: 'employer/:employerId', 16 | component: EmployerComponent, 17 | children: [ 18 | { 19 | path: 'clients', 20 | loadChildren: './clients/clients.module#ClientsModule' 21 | } 22 | ], 23 | canActivate: [AuthGuard] 24 | }, 25 | { 26 | path: 'employer/create', 27 | component: EmployerUpsertComponent, 28 | data: {type: 'create'}, 29 | canActivate: [AuthGuard] 30 | }, 31 | { 32 | path: 'employer/update/:id', 33 | component: EmployerUpsertComponent, 34 | data: {type: 'update'}, 35 | canActivate: [AuthGuard] 36 | } 37 | ]; 38 | 39 | @NgModule({ 40 | imports: [RouterModule.forChild(employerRoutes)], 41 | exports: [RouterModule] 42 | }) 43 | export class EmployerRoutingModule {} 44 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employerList/employerList.component.scss: -------------------------------------------------------------------------------- 1 | .action-header { 2 | padding-bottom: 15px; 3 | padding-top: 15px; 4 | } 5 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employerList/employerList.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewChild} from '@angular/core'; 2 | import {LoggerService} from 'src/app/core'; 3 | import {NotificationService} from 'src/app/shared/notifications/notificiation.service'; 4 | import {EmployerService} from '../services/employer.service'; 5 | import {Employer} from 'src/app/models/employer.model'; 6 | import {MatPaginator, MatTableDataSource} from '@angular/material'; 7 | 8 | @Component({ 9 | selector: 'app-employer-list', 10 | templateUrl: './employerList.component.html', 11 | styles: ['./employerList.component.scss'] 12 | }) 13 | export class EmployerListComponent implements OnInit { 14 | employers: Employer[] = new Array(); 15 | displayedColumns: string[] = ['name', 'description', 'created', 'actions']; 16 | @ViewChild(MatPaginator) 17 | paginator: MatPaginator; 18 | dataSource: MatTableDataSource; 19 | constructor( 20 | private _logger: LoggerService, 21 | private _notif: NotificationService, 22 | private _employerService: EmployerService 23 | ) {} 24 | 25 | async ngOnInit() { 26 | await this.getEmployers(); 27 | } 28 | 29 | async getEmployers() { 30 | await this._employerService.getAll().subscribe( 31 | res => { 32 | this._logger.info('Employers: %o', res); 33 | this.employers = res; 34 | this.dataSource = new MatTableDataSource(this.employers); 35 | this.dataSource.paginator = this.paginator; 36 | }, 37 | error => { 38 | this._logger.error(`error getting employers`); 39 | this._notif.show(`ERROR: ` + error); 40 | } 41 | ); 42 | } 43 | applyFilter(filterValue: string) { 44 | this.dataSource.filter = filterValue.trim().toLowerCase(); 45 | 46 | if (this.dataSource.paginator) { 47 | this.dataSource.paginator.firstPage(); 48 | } 49 | } 50 | 51 | deleteEmployer(employerId: string) { 52 | this._logger.info('DELETING EMPLOYER', employerId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employerUpsert/employerUpsert.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{pageAction}} Employer 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/employerUpsert/employerUpsert.component.scss: -------------------------------------------------------------------------------- 1 | .form-container { 2 | display: flex; 3 | flex-direction: column; 4 | input { 5 | width: 100%; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/root/employer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 15 | 16 |
17 |
18 | 19 | 20 | EMPLOYER: {{ employer.name }} 21 | 22 | 23 | {{ employer.description }} 24 | 25 | 26 | 27 | 28 | Content 1 29 | 30 | Content 2 31 | Content 3 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/root/employer.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/app/features/employer/root/employer.component.scss -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/root/employer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ActivatedRoute} from '@angular/router'; 3 | import {LoggerService} from 'src/app/core'; 4 | import {NotificationService} from 'src/app/shared/notifications'; 5 | import {EmployerService} from '../services/employer.service'; 6 | import {Employer} from 'src/app/models/employer.model'; 7 | import {Observable} from 'rxjs/internal/Observable'; 8 | 9 | @Component({ 10 | selector: 'app-employer', 11 | templateUrl: './employer.component.html', 12 | styleUrls: ['./employer.component.scss'] 13 | }) 14 | export class EmployerComponent implements OnInit { 15 | employer: Observable; 16 | employerId: string; 17 | 18 | constructor( 19 | private route: ActivatedRoute, 20 | private _logger: LoggerService, 21 | private _notif: NotificationService, 22 | private _employerService: EmployerService 23 | ) {} 24 | 25 | async ngOnInit() { 26 | this.employerId = this.route.snapshot.params['employerId']; 27 | 28 | this.employer = this._employerService.getOne(this.employerId); 29 | } 30 | async getEmployer() { 31 | return this._employerService.getOne(this.employerId).subscribe( 32 | res => { 33 | this._logger.info('EMPLOYER: ', res); 34 | // this.employer = res; 35 | }, 36 | error => { 37 | this._logger.error('error getting employer', error); 38 | this._notif.showError(`ERROR: ${error.message}`); 39 | } 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/employer/services/employer.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {AbstractRestService} from '../../../core'; 3 | import {Employer} from '../../../models/employer.model'; 4 | import {HttpClient} from '@angular/common/http'; 5 | 6 | @Injectable() 7 | export class EmployerService extends AbstractRestService { 8 | constructor(http: HttpClient) { 9 | super(http, '/api/employers'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/components/timeTracker/timeTracker.component.scss: -------------------------------------------------------------------------------- 1 | .table-container { 2 | display: flex; 3 | flex-direction: column; 4 | min-width: 300px; 5 | } 6 | .filter-header { 7 | min-height: 64px; 8 | padding: 8px 24px 0; 9 | } 10 | .mat-table { 11 | overflow: auto; 12 | max-height: 750px; 13 | width: 100%; 14 | } 15 | .mat-form-field { 16 | font-size: 14px; 17 | width: 100%; 18 | } 19 | 20 | .first-footer-row { 21 | font-weight: bold; 22 | } 23 | .second-footer-row td { 24 | color: #900000; 25 | } 26 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/components/timeTracker/timeTracker.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing'; 2 | 3 | import {TimeTrackerComponent} from './timeTracker.component'; 4 | 5 | describe('TimeclockComponent', () => { 6 | let component: TimeTrackerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [TimeTrackerComponent] 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(TimeTrackerComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/services/activities.service.ts: -------------------------------------------------------------------------------- 1 | import {AbstractRestService} from '../../../core/http/abstractRestService.service'; 2 | import {Injectable} from '@angular/core'; 3 | import {HttpClient} from '@angular/common/http'; 4 | import {environment} from '../../../../environments/environment'; 5 | import {Activity} from 'src/app/models/activity.model'; 6 | 7 | @Injectable() 8 | export class ActivitiesService extends AbstractRestService { 9 | constructor(http: HttpClient) { 10 | super(http, '/api/activities/'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/services/clients.service.ts: -------------------------------------------------------------------------------- 1 | import {AbstractRestService} from '../../../core/http/abstractRestService.service'; 2 | import {Injectable} from '@angular/core'; 3 | import {HttpClient} from '@angular/common/http'; 4 | import {Client} from 'src/app/models/client.model'; 5 | 6 | @Injectable() 7 | export class ClientsService extends AbstractRestService { 8 | constructor(http: HttpClient) { 9 | super(http, '/api/clients/'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/timeTracker.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {TimeTrackerComponent} from './components/timeTracker/timeTracker.component'; 3 | import {RouterModule} from '@angular/router'; 4 | import {SharedModule} from 'src/app/shared/shared.module'; 5 | import {TimeTrackerRoutingModule} from './timeTracker.routing'; 6 | import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 7 | import {ActivitiesService} from './services/activities.service'; 8 | import {ClientsService} from './services/clients.service'; 9 | @NgModule({ 10 | imports: [ 11 | SharedModule, 12 | TimeTrackerRoutingModule, 13 | FormsModule, 14 | ReactiveFormsModule 15 | ], 16 | declarations: [TimeTrackerComponent], 17 | entryComponents: [TimeTrackerComponent], 18 | providers: [ActivitiesService, ClientsService] 19 | }) 20 | export class TimeTrackerModule {} 21 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/features/timeTracker/timeTracker.routing.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {TimeTrackerComponent} from './components/timeTracker/timeTracker.component'; 4 | import {AuthGuard} from 'src/app/auth.guard'; 5 | 6 | const timeTrackerRoutes: Routes = [ 7 | { 8 | path: 'timeclock', 9 | component: TimeTrackerComponent, 10 | canActivate: [AuthGuard] 11 | } 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forChild(timeTrackerRoutes)], 16 | exports: [RouterModule] 17 | }) 18 | export class TimeTrackerRoutingModule {} 19 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/models/activity.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity} from './entity.model'; 2 | import * as moment from 'moment'; 3 | 4 | export class Activity extends Entity { 5 | name: string; 6 | description: string; 7 | dueDate: Date; 8 | clientId: string; 9 | constructor( 10 | name: string, 11 | description: string, 12 | dueDate: Date, 13 | clientId: string, 14 | createdDate: Date = new Date() 15 | ) { 16 | super(); 17 | this.name = name; 18 | this.description = description; 19 | this.dueDate = dueDate; 20 | this.clientId = clientId; 21 | this.created = createdDate; 22 | } 23 | } 24 | 25 | export class ActivitySerializer { 26 | fromJson(json: any): Activity { 27 | const activity = new Activity( 28 | json.name, 29 | json.description, 30 | json.dueDate, 31 | json.clientId, 32 | moment(json.cookedOn, 'mm-dd-yyyy hh:mm').toDate() 33 | ); 34 | return activity; 35 | } 36 | 37 | toJson(activity: Activity): any { 38 | return { 39 | id: activity.id, 40 | name: activity.name 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/models/client.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity} from './entity.model'; 2 | import * as moment from 'moment'; 3 | 4 | export class Client extends Entity { 5 | name: string; 6 | description: string; 7 | employerId: string; 8 | constructor( 9 | name: string, 10 | description: string, 11 | employerId: string, 12 | createdDate: Date = new Date() 13 | ) { 14 | super(); 15 | this.name = name; 16 | this.description = description; 17 | this.employerId = employerId; 18 | this.created = createdDate; 19 | } 20 | } 21 | 22 | export class ClientSerializer { 23 | fromJson(json: any): Client { 24 | const client = new Client( 25 | json.name, 26 | json.description, 27 | json.employerId, 28 | moment(json.cookedOn, 'mm-dd-yyyy hh:mm').toDate() 29 | ); 30 | return client; 31 | } 32 | 33 | toJson(client: Client): any { 34 | return { 35 | id: client.id, 36 | name: client.name 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/models/employer.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity} from './entity.model'; 2 | import * as moment from 'moment'; 3 | 4 | export class Employer extends Entity { 5 | name: string; 6 | description: string; 7 | constructor( 8 | name: string, 9 | description: string, 10 | createdDate: Date = new Date() 11 | ) { 12 | super(); 13 | this.name = name; 14 | this.description = description; 15 | this.created = createdDate; 16 | } 17 | } 18 | 19 | export class EmployerSerializer { 20 | fromJson(json: any): Employer { 21 | const employer = new Employer( 22 | json.name, 23 | json.description, 24 | moment(json.cookedOn, 'mm-dd-yyyy hh:mm').toDate() 25 | ); 26 | 27 | return employer; 28 | } 29 | 30 | toJson(employer: Employer): any { 31 | return { 32 | id: employer.id, 33 | name: employer.name 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/models/entity.model.ts: -------------------------------------------------------------------------------- 1 | export class Entity { 2 | id: string; 3 | created: Date; 4 | modified: Date; 5 | } 6 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/loading-spinner/loading-spinner.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
-------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/loading-spinner/loading-spinner.component.scss: -------------------------------------------------------------------------------- 1 | .spinner { 2 | width: 40px; 3 | height: 40px; 4 | background-color: #333; 5 | 6 | border-radius: 100%; 7 | -webkit-animation: sk-scaleout 1s infinite ease-in-out; 8 | animation: sk-scaleout 1s infinite ease-in-out; 9 | 10 | transform: translate(-50%, -50%); 11 | position: absolute; 12 | left: 50%; 13 | top: 48%; 14 | } 15 | 16 | @-webkit-keyframes sk-scaleout { 17 | 0% { 18 | -webkit-transform: scale(0); 19 | } 20 | 100% { 21 | -webkit-transform: scale(1); 22 | opacity: 0; 23 | } 24 | } 25 | 26 | @keyframes sk-scaleout { 27 | 0% { 28 | -webkit-transform: scale(0); 29 | transform: scale(0); 30 | } 31 | 100% { 32 | -webkit-transform: scale(1); 33 | transform: scale(1); 34 | opacity: 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/loading-spinner/loading-spinner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing'; 2 | import {LoadingSpinnerComponent} from './loading-spinner.component'; 3 | 4 | describe('LoadingSpinnerComponent', () => { 5 | let component: LoadingSpinnerComponent; 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(async(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [LoadingSpinnerComponent] 11 | }).compileComponents(); 12 | })); 13 | 14 | beforeEach(() => { 15 | fixture = TestBed.createComponent(LoadingSpinnerComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/loading-spinner/loading-spinner.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, OnDestroy} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-spinner', 5 | templateUrl: './loading-spinner.component.html', 6 | styleUrls: ['./loading-spinner.component.scss'] 7 | }) 8 | export class LoadingSpinnerComponent implements OnDestroy { 9 | private currentTimeout: number; 10 | public isDelayedRunning: boolean = false; 11 | 12 | @Input() 13 | public delay: number = 150; 14 | 15 | @Input() 16 | public set isRunning(value: boolean) { 17 | if (!value) { 18 | this.cancelTimeout(); 19 | this.isDelayedRunning = false; 20 | return; 21 | } 22 | 23 | if (this.currentTimeout) { 24 | return; 25 | } 26 | 27 | // specify window to side-step conflict with node types: https://github.com/mgechev/angular2-seed/issues/901 28 | this.currentTimeout = window.setTimeout(() => { 29 | this.isDelayedRunning = value; 30 | this.cancelTimeout(); 31 | }, this.delay); 32 | } 33 | 34 | private cancelTimeout(): void { 35 | clearTimeout(this.currentTimeout); 36 | this.currentTimeout = undefined; 37 | } 38 | 39 | ngOnDestroy(): any { 40 | this.cancelTimeout(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import { 3 | MatButtonModule, 4 | MatCheckboxModule, 5 | MatToolbarModule, 6 | MatIconModule, 7 | MatCardModule, 8 | MatProgressSpinnerModule, 9 | MatMenuModule, 10 | MatFormFieldModule, 11 | MatInputModule, 12 | MatSelectModule, 13 | MatSortModule, 14 | MatTableModule, 15 | MatSnackBarModule, 16 | MatListModule, 17 | MatTabsModule, 18 | MatProgressBarModule, 19 | MatPaginatorModule, 20 | MatDatepickerModule 21 | } from '@angular/material'; 22 | import {FormsModule} from '@angular/forms'; 23 | // import {CdkTableModule} from '@angular/cdk/table'; 24 | 25 | @NgModule({ 26 | imports: [ 27 | FormsModule, 28 | MatButtonModule, 29 | MatCheckboxModule, 30 | MatToolbarModule, 31 | MatIconModule, 32 | MatCardModule, 33 | MatProgressSpinnerModule, 34 | MatProgressBarModule, 35 | MatMenuModule, 36 | MatFormFieldModule, 37 | MatInputModule, 38 | MatSelectModule, 39 | MatSortModule, 40 | MatTableModule, 41 | MatSnackBarModule, 42 | MatListModule, 43 | MatTabsModule, 44 | MatPaginatorModule, 45 | MatDatepickerModule 46 | // CdkTableModule 47 | ], 48 | exports: [ 49 | FormsModule, 50 | MatButtonModule, 51 | MatCheckboxModule, 52 | MatToolbarModule, 53 | MatIconModule, 54 | MatCardModule, 55 | MatProgressSpinnerModule, 56 | MatProgressBarModule, 57 | MatMenuModule, 58 | MatFormFieldModule, 59 | MatInputModule, 60 | MatSelectModule, 61 | MatSortModule, 62 | MatTableModule, 63 | MatSnackBarModule, 64 | MatListModule, 65 | MatTabsModule, 66 | MatPaginatorModule, 67 | MatDatepickerModule 68 | // CdkTableModule 69 | ] 70 | }) 71 | export class MaterialModule {} 72 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/models/credentials.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Credentials { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/models/userRegistration.interface.ts: -------------------------------------------------------------------------------- 1 | export interface UserRegistration { 2 | email: string; 3 | password: string; 4 | firstName: string; 5 | lastName: string; 6 | userName: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/notifications/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './config'; 2 | export * from './notificiation.service'; 3 | export * from './snackBarMessage.model'; 4 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/notifications/notifStyle.enum.ts: -------------------------------------------------------------------------------- 1 | export enum NotifStyle { 2 | DEFAULT = '', 3 | INFO = 'blue-snackbar', 4 | SUCCESS = 'green-snackbar', 5 | ERROR = 'red-snackbar' 6 | } 7 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/notifications/snackBarMessage.model.ts: -------------------------------------------------------------------------------- 1 | import {MatSnackBarConfig} from '@angular/material'; 2 | 3 | export class SnackBarMessage { 4 | message: string; 5 | action: string = null; 6 | config: MatSnackBarConfig = null; 7 | } 8 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/services/base.service.ts: -------------------------------------------------------------------------------- 1 | import {Observable} from 'rxjs'; 2 | 3 | export abstract class BaseService { 4 | constructor() {} 5 | 6 | protected handleError(error: any) { 7 | const applicationError = error.headers.get('Application-Error'); 8 | 9 | // either applicationError in header or model error in body 10 | if (applicationError) { 11 | return Observable.throw(applicationError); 12 | } 13 | 14 | let modelStateErrors = ''; 15 | const serverError = error.json(); 16 | 17 | if (!serverError.type) { 18 | for (const key in serverError) { 19 | if (serverError[key]) { 20 | modelStateErrors += serverError[key] + '\n'; 21 | } 22 | } 23 | } 24 | 25 | modelStateErrors = modelStateErrors = '' ? null : modelStateErrors; 26 | return Observable.throw(modelStateErrors || 'Server error'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/shared.module.spec.ts: -------------------------------------------------------------------------------- 1 | import {SharedModule} from './shared.module'; 2 | 3 | describe('SharedModule', () => { 4 | let sharedModule: SharedModule; 5 | 6 | beforeEach(() => { 7 | sharedModule = new SharedModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(sharedModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {CommonModule} from '@angular/common'; 3 | import {MaterialModule} from './material/material.module'; 4 | import {RouterModule} from '@angular/router'; 5 | import {TopNavMenuComponent} from '../core/top-nav-menu/top-nav-menu.component'; 6 | import {LoadingSpinnerComponent} from './loading-spinner/loading-spinner.component'; 7 | import {NotificationService} from './notifications/notificiation.service'; 8 | 9 | @NgModule({ 10 | imports: [CommonModule, MaterialModule, RouterModule], 11 | exports: [ 12 | CommonModule, 13 | MaterialModule, 14 | TopNavMenuComponent, 15 | LoadingSpinnerComponent 16 | ], 17 | providers: [NotificationService], 18 | declarations: [TopNavMenuComponent, LoadingSpinnerComponent] 19 | }) 20 | export class SharedModule {} 21 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/assets/images/bovsi-owl-500x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/assets/images/bovsi-owl-500x250.png -------------------------------------------------------------------------------- /src/WebServer/AngularApp/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 -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | isDebugMode: false, 4 | apiBaseUrl: 'https://localhost:5002/api/' 5 | }; 6 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | isDebugMode: true, 8 | apiBaseUrl: 'https://localhost:5002/api/' 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/AngularApp/src/favicon.ico -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bovsi Timeclock 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | 14 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '~bootstrap/dist/css/bootstrap.css'; 3 | @import '~font-awesome/css/font-awesome.css'; 4 | @import '~@angular/material/theming'; 5 | /* @import "~@angular/material/prebuilt-themes/deeppurple-amber.css"; */ 6 | // @import '~@angular/material/prebuilt-themes/indigo-pink.css'; 7 | /* @import "~@angular/material/prebuilt-themes/pink-bluegrey.css"; */ 8 | /* @import "~@angular/material/prebuilt-themes/purple-green.css"; */ 9 | 10 | // $primary-color-dark: #1976d2; 11 | // $primary-color: #2196f3; 12 | // $primary-color-light: #bbdefb; 13 | // $primary-color-text: #ffffff; 14 | // $accent-color: #00bcd4; 15 | // $primary-text-color: #212121; 16 | // $secondary-text-color: #757575; 17 | // $divider-color: #bdbdbd; 18 | 19 | // import our custom themes 20 | @import 'bovsi-theme.scss'; 21 | @import 'bovsi-dark-theme.scss'; 22 | 23 | .bovsi-theme { 24 | @include angular-material-theme($bovsi-theme); 25 | } 26 | // additional css classes 27 | // you can hardcode one of them on the tag 28 | // or switch dynamically during runtime with [class] 29 | .bovsi-dark-theme { 30 | @include angular-material-theme($bovsi-dark-theme); 31 | } 32 | 33 | html, 34 | body { 35 | height: 100%; 36 | } 37 | // Notification / Material Snack Bar 38 | .blue-snackbar { 39 | background: #2196f3 !important; 40 | } 41 | .green-snackbar { 42 | background: #1de9b6 !important; 43 | } 44 | .red-snackbar { 45 | background: #b00020 !important; 46 | } 47 | 48 | .mat-icon { 49 | vertical-align: inherit !important; 50 | } 51 | 52 | .page-header { 53 | padding: 15px; 54 | } 55 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "emitDecoratorMetadata": true, 8 | "exclude": ["test.ts", "**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/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 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "app", "camelCase"], 5 | "component-selector": [true, "element", "app", "kebab-case"], 6 | "no-console": false, 7 | "quotemark": [true, "single", "jsx-double"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/WebServer/AngularApp/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": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/WebServer/Controllers/SampleDataController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace WebServer.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class SampleDataController : Controller 11 | { 12 | private static string[] Summaries = new[] 13 | { 14 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 15 | }; 16 | 17 | [HttpGet("[action]")] 18 | public IEnumerable WeatherForecasts() 19 | { 20 | var rng = new Random(); 21 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 22 | { 23 | DateFormatted = DateTime.UtcNow.AddDays(index).ToString("d"), 24 | TemperatureC = rng.Next(-20, 55), 25 | Summary = Summaries[rng.Next(Summaries.Length)] 26 | }); 27 | } 28 | 29 | public class WeatherForecast 30 | { 31 | public string DateFormatted { get; set; } 32 | public int TemperatureC { get; set; } 33 | public string Summary { get; set; } 34 | 35 | public int TemperatureF 36 | { 37 | get 38 | { 39 | return 32 + (int)(TemperatureC / 0.5556); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/WebServer/Helpers/Strings.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace WebServer 6 | { 7 | public static class Strings 8 | { 9 | public static string RemoveAllNonPrintableCharacters(string target) 10 | { 11 | return Regex.Replace(target, @"\p{C}+", string.Empty); 12 | } 13 | public static string ToUppercaseFirst(this string s) 14 | { 15 | if (string.IsNullOrEmpty(s)) 16 | { 17 | return string.Empty; 18 | } 19 | return char.ToUpper(s[0]) + s.Substring(1); 20 | } 21 | public static string TrimAndReduce(this string str) 22 | { 23 | return ConvertWhitespacesToSingleSpaces(str).Trim(); 24 | } 25 | 26 | public static string ConvertWhitespacesToSingleSpaces(this string value) 27 | { 28 | return Regex.Replace(value, @"\s+", " "); 29 | } 30 | public static int WordCount(this String str) 31 | { 32 | return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WebServer/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 Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /src/WebServer/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace WebServer.Pages 10 | { 11 | public class ErrorModel : PageModel 12 | { 13 | public string RequestId { get; set; } 14 | 15 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 16 | 17 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 18 | public void OnGet() 19 | { 20 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebServer/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using WebServer 2 | @namespace WebServer.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/WebServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // ! Used only as an example template. 3 | // ! Never store any ACTUAL settings in here. 4 | // ! This file will be checked into source control 5 | // ! and be used by developers to setup their own appsettings.{env.EnvrionmentName}.json file 6 | "Logging": { 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/WebServer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@angular/material-moment-adapter": { 6 | "version": "7.0.2", 7 | "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-7.0.2.tgz", 8 | "integrity": "sha512-vMlaa06apA10s2umyTupudfTudUHx3oJGv9nUsN5mgE8m548oTah3d15G5QGVj8z1wXagniRdIQR+4e8IoWnTQ==", 9 | "requires": { 10 | "tslib": "^1.7.1" 11 | } 12 | }, 13 | "tslib": { 14 | "version": "1.9.3", 15 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 16 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/WebServer/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maylordev/AspNetCore2Angular7/6b74d14d2e048385272d02cb1361e8e131cc0c13/src/WebServer/wwwroot/favicon.ico --------------------------------------------------------------------------------