From c84a7160e8d7a538979389f7b7308049c80ef7fd Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Thu, 2 Dec 2021 21:54:41 -0800 Subject: [PATCH 1/8] break into Core, Application, Infrastructure, Web Signed-off-by: Matthew Fisher --- .devcontainer/devcontainer.json | 6 +- .gitattributes | 2 +- .github/dependabot.yml | 2 +- .github/release-image/Dockerfile | 14 +- .github/release-image/localhost.conf | 2 +- .github/workflows/configuration.json | 2 +- .gitignore | 2 +- .vscode/launch.json | 4 +- .vscode/tasks.json | 8 +- Hippo.sln | 68 +- scripts/build-image.sh | 6 +- .../Accounts/Commands/CreateAccountCommand.cs | 33 + .../Commands/CreateAccountCommandValidator.cs | 38 + .../Accounts/Commands/CreateTokenCommand.cs | 42 ++ .../Accounts/Commands/LoginAccountCommand.cs | 40 + .../Accounts/Commands/LogoutAccountCommand.cs | 26 + src/Application/Application.csproj | 23 + .../Apps/Commands/CreateAppCommand.cs | 40 + .../Commands/CreateAppCommandValidator.cs | 16 + .../Apps/Commands/DeleteAppCommand.cs | 40 + .../Apps/Commands/PurgeAppsCommand.cs | 30 + .../Apps/Commands/UpdateAppCommand.cs | 44 ++ .../Commands/UpdateAppCommandValidator.cs | 29 + .../EventHandlers/AppCreatedEventHandler.cs | 25 + .../EventHandlers/AppDeletedEventHandler.cs | 25 + src/Application/Apps/Queries/AppDto.cs | 25 + src/Application/Apps/Queries/AppRecord.cs | 23 + src/Application/Apps/Queries/AppsVm.cs | 6 + .../Apps/Queries/ExportAppsQuery.cs | 38 + src/Application/Apps/Queries/ExportAppsVm.cs | 17 + src/Application/Apps/Queries/GetAppQuery.cs | 42 ++ src/Application/Apps/Queries/GetAppsQuery.cs | 35 + .../Channels/Commands/CreateChannelCommand.cs | 51 ++ .../Commands/CreateChannelCommandValidator.cs | 13 + .../Channels/Commands/DeleteChannelCommand.cs | 40 + .../Channels/Commands/PurgeChannelsCommand.cs | 30 + .../Channels/Commands/UpdateChannelCommand.cs | 56 ++ .../ActiveRevisionChangedEventHandler.cs | 42 ++ .../ChannelCreatedEventHandler.cs | 25 + .../ChannelDeletedEventHandler.cs | 25 + .../Channels/Queries/ChannelDto.cs | 30 + .../Channels/Queries/ChannelRecord.cs | 28 + .../Channels/Queries/ChannelsVm.cs | 6 + .../Channels/Queries/ExportChannelsQuery.cs | 38 + .../Channels/Queries/ExportChannelsVm.cs | 17 + .../Channels/Queries/GetChannelsQuery.cs | 35 + .../Behaviours/AuthorizationBehaviour.cs | 80 ++ .../Common/Behaviours/LoggingBehaviour.cs | 34 + .../Common/Behaviours/PerformanceBehaviour.cs | 54 ++ .../Behaviours/UnhandledExceptionBehaviour.cs | 30 + .../Common/Behaviours/ValidationBehaviour.cs | 37 + .../Exceptions/CreateFailedException.cs | 24 + .../Exceptions/ForbiddenAccessException.cs | 6 + .../Common/Exceptions/LoginFailedException.cs | 25 + .../Common/Exceptions/NotFoundException.cs | 24 + .../Common/Exceptions/ValidationException.cs | 22 + .../Interfaces/IApplicationDbContext.cs | 19 + .../Common/Interfaces/ICurrentUserService.cs | 6 + .../Common/Interfaces/IDateTime.cs | 8 + .../Common/Interfaces/IDomainEventService.cs | 8 + .../Common/Interfaces/IIdentityService.cs | 20 + .../Common/Interfaces/IJobScheduler.cs | 16 + .../Common/Interfaces/IJsonFileBuilder.cs | 20 + .../Common/Interfaces/IReverseProxy.cs | 10 + .../Common/Interfaces/ISignInService.cs | 17 + .../Common/Interfaces/ITaskQueue.cs | 10 + .../Common/Interfaces/ITokenService.cs | 19 + src/Application/Common/Mappings/IMapFrom.cs | 8 + .../Common/Mappings/MappingExtensions.cs | 15 + .../Common/Mappings/MappingProfile.cs | 31 + .../Common/Models/DomainEventNotification.cs | 14 + .../Common/Models/PaginatedList.cs | 31 + src/Application/Common/Models/Result.cs | 24 + .../Common/Security/AuthorizeAttribute.cs | 23 + src/Application/DependencyInjection.cs | 23 + .../Domains/Commands/CreateDomainCommand.cs | 37 + .../Commands/CreateDomainCommandValidator.cs | 13 + .../Domains/Commands/DeleteDomainCommand.cs | 40 + .../Domains/Commands/PurgeDomainsCommand.cs | 30 + .../DomainCreatedEventHandler.cs | 25 + .../DomainDeletedEventHandler.cs | 25 + src/Application/Domains/Queries/DomainDto.cs | 13 + .../Domains/Queries/DomainRecord.cs | 9 + src/Application/Domains/Queries/DomainsVm.cs | 6 + .../Domains/Queries/ExportDomainsQuery.cs | 38 + .../Domains/Queries/ExportDomainsVm.cs | 17 + .../Domains/Queries/GetDomainsQuery.cs | 35 + .../CreateEnvironmentVariableCommand.cs | 40 + ...eateEnvironmentVariableCommandValidator.cs | 17 + .../DeleteEnvironmentVariableCommand.cs | 40 + .../PurgeEnvironmentVariablesCommand.cs | 30 + .../EnvironmentVariableCreatedEventHandler.cs | 25 + .../EnvironmentVariableDeletedEventHandler.cs | 25 + .../Queries/EnvironmentVariableDto.cs | 15 + .../Queries/EnvironmentVariableRecord.cs | 11 + .../Queries/EnvironmentVariablesVm.cs | 6 + .../ExportEnvironmentVariablesQuery.cs | 39 + .../Queries/ExportEnvironmentVariablesVm.cs | 17 + .../Queries/GetEnvironmentVariablesQuery.cs | 35 + .../Commands/CreateRevisionCommand.cs | 40 + .../CreateRevisionCommandValidator.cs | 13 + .../Commands/DeleteRevisionCommand.cs | 40 + .../Commands/PurgeRevisionsCommand.cs | 30 + .../RevisionCreatedEventHandler.cs | 51 ++ .../RevisionDeletedEventHandler.cs | 51 ++ .../Revisions/Queries/ExportRevisionsQuery.cs | 38 + .../Revisions/Queries/ExportRevisionsVm.cs | 17 + .../Revisions/Queries/GetRevisionsQuery.cs | 35 + .../Revisions/Queries/RevisionDto.cs | 22 + .../Revisions/Queries/RevisionRecord.cs | 9 + .../Revisions/Queries/RevisionsVm.cs | 6 + .../Rules/RevisionRangeRule.cs | 13 +- src/Core/Common/AuditableEntity.cs | 20 + src/Core/Common/DomainEvent.cs | 16 + src/Core/Common/ValueObject.cs | 40 + src/Core/Core.csproj | 11 + src/Core/Entities/App.cs | 22 + src/Core/Entities/Channel.cs | 28 + src/Core/Entities/Domain.cs | 14 + src/Core/Entities/EnvironmentVariable.cs | 16 + src/Core/Entities/Revision.cs | 16 + .../Enums/ChannelRevisionSelectionStrategy.cs | 16 + src/Core/Events/ActiveRevisionChangedEvent.cs | 17 + src/Core/Events/AppCreatedEvent.cs | 11 + src/Core/Events/AppDeletedEvent.cs | 11 + src/Core/Events/ChannelCreatedEvent.cs | 11 + src/Core/Events/ChannelDeletedEvent.cs | 11 + src/Core/Events/DomainCreatedEvent.cs | 11 + src/Core/Events/DomainDeletedEvent.cs | 11 + .../Events/EnvironmentVariableCreatedEvent.cs | 11 + .../Events/EnvironmentVariableDeletedEvent.cs | 11 + src/Core/Events/RevisionCreatedEvent.cs | 11 + src/Core/Events/RevisionDeletedEvent.cs | 11 + .../Exceptions/InvalidRevisionRangeRule.cs | 9 + src/Core/_Imports.cs | 5 + src/Hippo/Api/ApplicationController.cs | 97 --- src/Hippo/Api/ChannelController.cs | 164 ---- src/Hippo/Config/ChannelConfig.cs | 27 - .../Config/ChannelConfigurationProvider.cs | 81 -- .../Config/ChannelConfigurationSource.cs | 14 - .../Config/ConfigurationBuilderExtensions.cs | 11 - .../Config/IChannelConfigurationProvider.cs | 10 - src/Hippo/Controllers/AccountController.cs | 272 ------- src/Hippo/Controllers/AdminController.cs | 14 - src/Hippo/Controllers/AppController.cs | 501 ------------- .../Controllers/ApplicationControllerCore.cs | 110 --- src/Hippo/Controllers/BaseController.cs | 83 --- src/Hippo/Controllers/RevisionController.cs | 110 --- .../ChannelConfigProviderExtensions.cs | 20 - src/Hippo/Hippo.csproj | 56 -- src/Hippo/Logging/ITraceable.cs | 6 - src/Hippo/Messages/ApplicationMessage.cs | 30 - src/Hippo/Messages/ChannelMessage.cs | 51 -- .../Messages/CreateApplicationRequest.cs | 17 - .../Messages/CreateApplicationResponse.cs | 24 - src/Hippo/Messages/CreateChannelRequest.cs | 87 --- src/Hippo/Messages/CreateChannelResponse.cs | 25 - src/Hippo/Messages/GetChannelResponse.cs | 16 - src/Hippo/Messages/RegisterRevisionRequest.cs | 42 -- .../20210706011250_Initial.Designer.cs | 605 --------------- .../Postgres/20210706011250_Initial.cs | 480 ------------ .../20210712232619_EventHistory.Designer.cs | 648 ---------------- .../Postgres/20210712232619_EventHistory.cs | 37 - .../20210722051212_Timestamp.Designer.cs | 648 ---------------- .../Postgres/20210722051212_Timestamp.cs | 17 - .../20210726214502_Collaborations.Designer.cs | 687 ----------------- .../Postgres/20210726214502_Collaborations.cs | 85 --- ...20210803220759_CascadingDelete.Designer.cs | 690 ----------------- .../20210803220759_CascadingDelete.cs | 85 --- ...803222847_DomainOwnedByChannel.Designer.cs | 701 ------------------ .../20210803222847_DomainOwnedByChannel.cs | 78 -- .../PostgresDataContextModelSnapshot.cs | 699 ----------------- .../20210712232550_EventHistory.Designer.cs | 643 ---------------- .../Sqlite/20210712232550_EventHistory.cs | 37 - .../Sqlite/20210722051132_Timestamp.cs | 334 --------- .../20210726214403_Collaborations.Designer.cs | 682 ----------------- .../Sqlite/20210726214403_Collaborations.cs | 85 --- ...20210803220823_CascadingDelete.Designer.cs | 685 ----------------- .../Sqlite/20210803220823_CascadingDelete.cs | 85 --- ...803222521_DomainOwnedByChannel.Designer.cs | 696 ----------------- .../20210803222521_DomainOwnedByChannel.cs | 78 -- .../Sqlite/SqliteDataContextModelSnapshot.cs | 694 ----------------- src/Hippo/Models/Account.cs | 16 - src/Hippo/Models/Application.cs | 75 -- src/Hippo/Models/BaseEntity.cs | 23 - src/Hippo/Models/Channel.cs | 115 --- src/Hippo/Models/Collaboration.cs | 11 - src/Hippo/Models/Configuration.cs | 10 - src/Hippo/Models/DataContext.cs | 154 ---- src/Hippo/Models/Domain.cs | 13 - src/Hippo/Models/EnvironmentVariable.cs | 14 - src/Hippo/Models/EventLogEntry.cs | 60 -- src/Hippo/Models/HealthStatus.cs | 30 - src/Hippo/Models/Key.cs | 18 - src/Hippo/Models/Revision.cs | 27 - .../ICreateApplicationParameters.cs | 7 - .../OperationData/ICreateChannelParameters.cs | 16 - src/Hippo/Program.cs | 222 ------ src/Hippo/Proxies/ChannelConfigProvider.cs | 112 --- src/Hippo/Proxies/IReverseProxy.cs | 9 - src/Hippo/Proxies/IReverseProxyUpdater.cs | 9 - src/Hippo/Proxies/ProxyStartup.cs | 36 - src/Hippo/Proxies/YarpReverseProxy.cs | 28 - .../Proxies/appsettings.Development.json | 20 - src/Hippo/Proxies/appsettings.json | 17 - .../Repositories/ActionContextCurrentUser.cs | 15 - src/Hippo/Repositories/DbAccountRepository.cs | 17 - .../Repositories/DbApplicationRepository.cs | 75 -- src/Hippo/Repositories/DbChannelRepository.cs | 60 -- .../Repositories/DbEventLogRepository.cs | 113 --- .../Repositories/DbRevisionRepository.cs | 19 - src/Hippo/Repositories/DbUnitOfWork.cs | 45 -- src/Hippo/Repositories/IAccountRepository.cs | 6 - .../Repositories/IApplicationRepository.cs | 18 - src/Hippo/Repositories/IChannelRepository.cs | 13 - src/Hippo/Repositories/ICurrentUser.cs | 7 - src/Hippo/Repositories/IEventLogRepository.cs | 19 - src/Hippo/Repositories/IRevisionRepository.cs | 8 - src/Hippo/Repositories/IUnitOfWork.cs | 15 - src/Hippo/Schedulers/JobScheduler.cs | 79 -- .../Schedulers/WagiDotnetJobScheduler.cs | 35 - .../Tasks/ChannelUpdateBackgroundService.cs | 74 -- .../ReverseProxyUpdateBackgroundService.cs | 62 -- src/Hippo/ViewModels/AccountRegisterForm.cs | 28 - src/Hippo/ViewModels/ApiLoginForm.cs | 19 - src/Hippo/ViewModels/AppDetails.cs | 22 - src/Hippo/ViewModels/AppEditChannelForm.cs | 37 - src/Hippo/ViewModels/AppEditForm.cs | 54 -- src/Hippo/ViewModels/AppNewChannelForm.cs | 133 ---- src/Hippo/ViewModels/AppNewForm.cs | 41 - .../ViewModels/AppRegisterRevisionForm.cs | 17 - src/Hippo/ViewModels/Converters.cs | 20 - src/Hippo/ViewModels/LoginForm.cs | 21 - .../ViewModels/RevisionRegistrationForm.cs | 17 - src/Hippo/Views/Admin/Terminal.cshtml | 53 -- src/Hippo/Views/App/Details.cshtml | 192 ----- src/Hippo/Views/App/NewChannel.cshtml | 135 ---- src/Hippo/Views/Home/Index.cshtml | 3 - .../Shared/_TerminalScriptsPartial.cshtml | 1 - .../Shared/_TerminalStylesPartial.cshtml | 1 - src/Hippo/Views/_ViewImports.cshtml | 3 - src/Hippo/WagiDotnet/WagiDotnetStartup.cs | 39 - .../WagiDotnet/appsettings.Development.json | 8 - src/Hippo/WagiDotnet/appsettings.json | 19 - src/Hippo/appsettings.Development.json | 19 - .../Data/ApplicationDbContext.cs | 85 +++ .../Data/ApplicationDbContextSeed.cs | 17 + .../Data/Configurations/AppConfiguration.cs | 17 + .../Configurations/ChannelConfiguration.cs | 13 + .../Configurations/DomainConfiguration.cs | 13 + .../EnvironmentVariableConfiguration.cs | 13 + .../Configurations/RevisionConfiguration.cs | 13 + .../20211210182727_Initial.Designer.cs} | 408 ++++------ .../Migrations/20211210182727_Initial.cs} | 334 +++------ .../ApplicationDbContextModelSnapshot.cs} | 427 ++++------- src/Infrastructure/DependencyInjection.cs | 79 ++ .../InvalidDatabaseDriverException.cs | 9 + src/Infrastructure/Files/JsonFileBuilder.cs | 77 ++ src/Infrastructure/Identity/Account.cs | 7 + .../Identity/IdentityResultExtensions.cs | 14 + .../Identity/IdentityService.cs | 105 +++ .../Identity/SignInResultExtensions.cs | 34 + src/Infrastructure/Identity/SignInService.cs | 39 + src/Infrastructure/Infrastructure.csproj | 26 + .../JobSchedulers/LocalJobScheduler.cs} | 84 ++- .../Configuration/InMemoryConfigProvider.cs | 83 +++ .../ReverseProxies/YarpReverseProxy.cs | 63 ++ .../Services/DateTimeService.cs | 10 + .../Services/DomainEventService.cs | 32 + src/Infrastructure/Services/TokenService.cs | 40 + .../Tasks/TaskQueue.cs | 14 +- src/{Hippo => Web}/.vscode/launch.json | 0 src/{Hippo => Web}/.vscode/tasks.json | 2 +- src/Web/Api/AccountController.cs | 22 + src/Web/Api/ApiControllerBase.cs | 11 + src/Web/Api/AppController.cs | 38 + src/Web/Api/ChannelController.cs | 40 + src/Web/Api/DomainController.cs | 40 + src/Web/Api/EnvironmentVariableController.cs | 40 + src/Web/Api/RevisionController.cs | 40 + src/Web/Controllers/AccountController.cs | 64 ++ src/Web/Controllers/AppController.cs | 136 ++++ .../Controllers/StyleguideController.cs | 4 +- src/Web/Controllers/WebUIControllerBase.cs | 11 + src/Web/Program.cs | 100 +++ .../Properties/launchSettings.json | 2 +- src/Web/Services/CurrentUserService.cs | 16 + src/{Hippo => Web}/Views/Account/Index.cshtml | 0 src/{Hippo => Web}/Views/Account/Login.cshtml | 2 +- .../Views/Account/Register.cshtml | 12 +- src/{Hippo => Web}/Views/App/Delete.cshtml | 2 +- .../Views/App/DeleteChannel.cshtml | 4 +- src/Web/Views/App/Details.cshtml | 6 + src/{Hippo => Web}/Views/App/Edit.cshtml | 14 +- .../Views/App/EditChannel.cshtml | 64 +- src/{Hippo => Web}/Views/App/Index.cshtml | 9 +- src/{Hippo => Web}/Views/App/New.cshtml | 2 +- src/Web/Views/App/NewChannel.cshtml | 103 +++ .../Views/App/RegisterRevision.cshtml | 4 +- .../Views/Shared/_AppLayout.cshtml | 0 .../Views/Shared/_Footer.cshtml | 0 .../Views/Shared/_Layout.cshtml | 18 +- .../Views/Shared/_NavAppTopbar.cshtml | 3 +- .../Views/Shared/_NavBreadcrumb.cshtml | 0 .../Views/Shared/_NavSidebar.cshtml | 25 +- .../Views/Shared/_NavTopbar.cshtml | 3 +- .../Shared/_ValidationScriptsPartial.cshtml | 0 .../Views/Styleguide/Index.cshtml | 0 src/Web/Views/_ViewImports.cshtml | 1 + src/{Hippo => Web}/Views/_ViewStart.cshtml | 0 src/Web/Web.csproj | 50 ++ src/Web/appsettings.Development.json | 28 + src/{Hippo => Web}/appsettings.json | 9 +- src/{Hippo => Web}/assets/js/app.js | 2 +- src/{Hippo => Web}/assets/js/dist/app.dev.js | 4 +- .../assets/styles/dist/hippo.css | 0 src/{Hippo => Web}/assets/styles/hippo.scss | 0 src/{Hippo => Web}/gulpfile.js | 0 src/{Hippo => Web}/nuget.config | 2 +- src/{Hippo => Web}/package-lock.json | 2 +- src/{Hippo => Web}/package.json | 0 .../Application.UnitTests.csproj | 32 + .../Common/Behaviours/RequestLoggerTests.cs | 47 ++ .../Exceptions/ValidationExceptionTests.cs | 68 ++ .../Common/Mappings/MappingTests.cs | 61 ++ .../Common/AuditableEntityTests.cs | 29 + .../Core.UnitTests/Common/DomainEventTests.cs | 23 + tests/Core.UnitTests/Core.UnitTests.csproj | 29 + .../ChannelRevisionSelectionStrategyTests.cs | 19 + tests/Core.UnitTests/Events/EventTests.cs | 92 +++ .../Exceptions/ExceptionTests.cs | 14 + .../Accounts/Commands/CreateAccountTests.cs | 60 ++ .../Controllers/AccountController.cs | 67 -- .../Fakes/FakeCurrentUser.cs | 15 - .../Fakes/FakeSignInManager.cs | 32 - .../Fakes/FakeUserManager.cs | 63 -- .../Hippo.FunctionalTests/Fakes/NullLogger.cs | 26 - .../Hippo.FunctionalTests.csproj | 58 +- tests/Hippo.FunctionalTests/TestBase.cs | 197 +++++ tests/Hippo.FunctionalTests/appsettings.json | 7 + tests/Hippo.UnitTests/Hippo.UnitTests.csproj | 25 - .../Hippo.UnitTests/Models/ApplicationTest.cs | 64 -- tests/Hippo.UnitTests/Models/ChannelTest.cs | 93 --- .../Rules/RevisionRangeRuleTests.cs | 93 --- 344 files changed, 5882 insertions(+), 15414 deletions(-) create mode 100644 src/Application/Accounts/Commands/CreateAccountCommand.cs create mode 100644 src/Application/Accounts/Commands/CreateAccountCommandValidator.cs create mode 100644 src/Application/Accounts/Commands/CreateTokenCommand.cs create mode 100644 src/Application/Accounts/Commands/LoginAccountCommand.cs create mode 100644 src/Application/Accounts/Commands/LogoutAccountCommand.cs create mode 100644 src/Application/Application.csproj create mode 100644 src/Application/Apps/Commands/CreateAppCommand.cs create mode 100644 src/Application/Apps/Commands/CreateAppCommandValidator.cs create mode 100644 src/Application/Apps/Commands/DeleteAppCommand.cs create mode 100644 src/Application/Apps/Commands/PurgeAppsCommand.cs create mode 100644 src/Application/Apps/Commands/UpdateAppCommand.cs create mode 100644 src/Application/Apps/Commands/UpdateAppCommandValidator.cs create mode 100644 src/Application/Apps/EventHandlers/AppCreatedEventHandler.cs create mode 100644 src/Application/Apps/EventHandlers/AppDeletedEventHandler.cs create mode 100644 src/Application/Apps/Queries/AppDto.cs create mode 100644 src/Application/Apps/Queries/AppRecord.cs create mode 100644 src/Application/Apps/Queries/AppsVm.cs create mode 100644 src/Application/Apps/Queries/ExportAppsQuery.cs create mode 100644 src/Application/Apps/Queries/ExportAppsVm.cs create mode 100644 src/Application/Apps/Queries/GetAppQuery.cs create mode 100644 src/Application/Apps/Queries/GetAppsQuery.cs create mode 100644 src/Application/Channels/Commands/CreateChannelCommand.cs create mode 100644 src/Application/Channels/Commands/CreateChannelCommandValidator.cs create mode 100644 src/Application/Channels/Commands/DeleteChannelCommand.cs create mode 100644 src/Application/Channels/Commands/PurgeChannelsCommand.cs create mode 100644 src/Application/Channels/Commands/UpdateChannelCommand.cs create mode 100644 src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs create mode 100644 src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs create mode 100644 src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs create mode 100644 src/Application/Channels/Queries/ChannelDto.cs create mode 100644 src/Application/Channels/Queries/ChannelRecord.cs create mode 100644 src/Application/Channels/Queries/ChannelsVm.cs create mode 100644 src/Application/Channels/Queries/ExportChannelsQuery.cs create mode 100644 src/Application/Channels/Queries/ExportChannelsVm.cs create mode 100644 src/Application/Channels/Queries/GetChannelsQuery.cs create mode 100644 src/Application/Common/Behaviours/AuthorizationBehaviour.cs create mode 100644 src/Application/Common/Behaviours/LoggingBehaviour.cs create mode 100644 src/Application/Common/Behaviours/PerformanceBehaviour.cs create mode 100644 src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs create mode 100644 src/Application/Common/Behaviours/ValidationBehaviour.cs create mode 100644 src/Application/Common/Exceptions/CreateFailedException.cs create mode 100644 src/Application/Common/Exceptions/ForbiddenAccessException.cs create mode 100644 src/Application/Common/Exceptions/LoginFailedException.cs create mode 100644 src/Application/Common/Exceptions/NotFoundException.cs create mode 100644 src/Application/Common/Exceptions/ValidationException.cs create mode 100644 src/Application/Common/Interfaces/IApplicationDbContext.cs create mode 100644 src/Application/Common/Interfaces/ICurrentUserService.cs create mode 100644 src/Application/Common/Interfaces/IDateTime.cs create mode 100644 src/Application/Common/Interfaces/IDomainEventService.cs create mode 100644 src/Application/Common/Interfaces/IIdentityService.cs create mode 100644 src/Application/Common/Interfaces/IJobScheduler.cs create mode 100644 src/Application/Common/Interfaces/IJsonFileBuilder.cs create mode 100644 src/Application/Common/Interfaces/IReverseProxy.cs create mode 100644 src/Application/Common/Interfaces/ISignInService.cs create mode 100644 src/Application/Common/Interfaces/ITaskQueue.cs create mode 100644 src/Application/Common/Interfaces/ITokenService.cs create mode 100644 src/Application/Common/Mappings/IMapFrom.cs create mode 100644 src/Application/Common/Mappings/MappingExtensions.cs create mode 100644 src/Application/Common/Mappings/MappingProfile.cs create mode 100644 src/Application/Common/Models/DomainEventNotification.cs create mode 100644 src/Application/Common/Models/PaginatedList.cs create mode 100644 src/Application/Common/Models/Result.cs create mode 100644 src/Application/Common/Security/AuthorizeAttribute.cs create mode 100644 src/Application/DependencyInjection.cs create mode 100644 src/Application/Domains/Commands/CreateDomainCommand.cs create mode 100644 src/Application/Domains/Commands/CreateDomainCommandValidator.cs create mode 100644 src/Application/Domains/Commands/DeleteDomainCommand.cs create mode 100644 src/Application/Domains/Commands/PurgeDomainsCommand.cs create mode 100644 src/Application/Domains/EventHandlers/DomainCreatedEventHandler.cs create mode 100644 src/Application/Domains/EventHandlers/DomainDeletedEventHandler.cs create mode 100644 src/Application/Domains/Queries/DomainDto.cs create mode 100644 src/Application/Domains/Queries/DomainRecord.cs create mode 100644 src/Application/Domains/Queries/DomainsVm.cs create mode 100644 src/Application/Domains/Queries/ExportDomainsQuery.cs create mode 100644 src/Application/Domains/Queries/ExportDomainsVm.cs create mode 100644 src/Application/Domains/Queries/GetDomainsQuery.cs create mode 100644 src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommand.cs create mode 100644 src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommandValidator.cs create mode 100644 src/Application/EnvironmentVariables/Commands/DeleteEnvironmentVariableCommand.cs create mode 100644 src/Application/EnvironmentVariables/Commands/PurgeEnvironmentVariablesCommand.cs create mode 100644 src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableCreatedEventHandler.cs create mode 100644 src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableDeletedEventHandler.cs create mode 100644 src/Application/EnvironmentVariables/Queries/EnvironmentVariableDto.cs create mode 100644 src/Application/EnvironmentVariables/Queries/EnvironmentVariableRecord.cs create mode 100644 src/Application/EnvironmentVariables/Queries/EnvironmentVariablesVm.cs create mode 100644 src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesQuery.cs create mode 100644 src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesVm.cs create mode 100644 src/Application/EnvironmentVariables/Queries/GetEnvironmentVariablesQuery.cs create mode 100644 src/Application/Revisions/Commands/CreateRevisionCommand.cs create mode 100644 src/Application/Revisions/Commands/CreateRevisionCommandValidator.cs create mode 100644 src/Application/Revisions/Commands/DeleteRevisionCommand.cs create mode 100644 src/Application/Revisions/Commands/PurgeRevisionsCommand.cs create mode 100644 src/Application/Revisions/EventHandlers/RevisionCreatedEventHandler.cs create mode 100644 src/Application/Revisions/EventHandlers/RevisionDeletedEventHandler.cs create mode 100644 src/Application/Revisions/Queries/ExportRevisionsQuery.cs create mode 100644 src/Application/Revisions/Queries/ExportRevisionsVm.cs create mode 100644 src/Application/Revisions/Queries/GetRevisionsQuery.cs create mode 100644 src/Application/Revisions/Queries/RevisionDto.cs create mode 100644 src/Application/Revisions/Queries/RevisionRecord.cs create mode 100644 src/Application/Revisions/Queries/RevisionsVm.cs rename src/{Hippo => Application}/Rules/RevisionRangeRule.cs (94%) create mode 100644 src/Core/Common/AuditableEntity.cs create mode 100644 src/Core/Common/DomainEvent.cs create mode 100644 src/Core/Common/ValueObject.cs create mode 100644 src/Core/Core.csproj create mode 100644 src/Core/Entities/App.cs create mode 100644 src/Core/Entities/Channel.cs create mode 100644 src/Core/Entities/Domain.cs create mode 100644 src/Core/Entities/EnvironmentVariable.cs create mode 100644 src/Core/Entities/Revision.cs create mode 100644 src/Core/Enums/ChannelRevisionSelectionStrategy.cs create mode 100644 src/Core/Events/ActiveRevisionChangedEvent.cs create mode 100644 src/Core/Events/AppCreatedEvent.cs create mode 100644 src/Core/Events/AppDeletedEvent.cs create mode 100644 src/Core/Events/ChannelCreatedEvent.cs create mode 100644 src/Core/Events/ChannelDeletedEvent.cs create mode 100644 src/Core/Events/DomainCreatedEvent.cs create mode 100644 src/Core/Events/DomainDeletedEvent.cs create mode 100644 src/Core/Events/EnvironmentVariableCreatedEvent.cs create mode 100644 src/Core/Events/EnvironmentVariableDeletedEvent.cs create mode 100644 src/Core/Events/RevisionCreatedEvent.cs create mode 100644 src/Core/Events/RevisionDeletedEvent.cs create mode 100644 src/Core/Exceptions/InvalidRevisionRangeRule.cs create mode 100644 src/Core/_Imports.cs delete mode 100644 src/Hippo/Api/ApplicationController.cs delete mode 100644 src/Hippo/Api/ChannelController.cs delete mode 100644 src/Hippo/Config/ChannelConfig.cs delete mode 100644 src/Hippo/Config/ChannelConfigurationProvider.cs delete mode 100644 src/Hippo/Config/ChannelConfigurationSource.cs delete mode 100644 src/Hippo/Config/ConfigurationBuilderExtensions.cs delete mode 100644 src/Hippo/Config/IChannelConfigurationProvider.cs delete mode 100644 src/Hippo/Controllers/AccountController.cs delete mode 100644 src/Hippo/Controllers/AdminController.cs delete mode 100644 src/Hippo/Controllers/AppController.cs delete mode 100644 src/Hippo/Controllers/ApplicationControllerCore.cs delete mode 100644 src/Hippo/Controllers/BaseController.cs delete mode 100644 src/Hippo/Controllers/RevisionController.cs delete mode 100644 src/Hippo/Extensions/ChannelConfigProviderExtensions.cs delete mode 100644 src/Hippo/Hippo.csproj delete mode 100644 src/Hippo/Logging/ITraceable.cs delete mode 100644 src/Hippo/Messages/ApplicationMessage.cs delete mode 100644 src/Hippo/Messages/ChannelMessage.cs delete mode 100644 src/Hippo/Messages/CreateApplicationRequest.cs delete mode 100644 src/Hippo/Messages/CreateApplicationResponse.cs delete mode 100644 src/Hippo/Messages/CreateChannelRequest.cs delete mode 100644 src/Hippo/Messages/CreateChannelResponse.cs delete mode 100644 src/Hippo/Messages/GetChannelResponse.cs delete mode 100644 src/Hippo/Messages/RegisterRevisionRequest.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210706011250_Initial.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210706011250_Initial.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210712232619_EventHistory.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210712232619_EventHistory.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210722051212_Timestamp.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210722051212_Timestamp.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210726214502_Collaborations.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210726214502_Collaborations.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.Designer.cs delete mode 100644 src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.cs delete mode 100644 src/Hippo/Migrations/Postgres/PostgresDataContextModelSnapshot.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.Designer.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.Designer.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.Designer.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.Designer.cs delete mode 100644 src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.cs delete mode 100644 src/Hippo/Migrations/Sqlite/SqliteDataContextModelSnapshot.cs delete mode 100644 src/Hippo/Models/Account.cs delete mode 100644 src/Hippo/Models/Application.cs delete mode 100644 src/Hippo/Models/BaseEntity.cs delete mode 100644 src/Hippo/Models/Channel.cs delete mode 100644 src/Hippo/Models/Collaboration.cs delete mode 100644 src/Hippo/Models/Configuration.cs delete mode 100644 src/Hippo/Models/DataContext.cs delete mode 100644 src/Hippo/Models/Domain.cs delete mode 100644 src/Hippo/Models/EnvironmentVariable.cs delete mode 100644 src/Hippo/Models/EventLogEntry.cs delete mode 100644 src/Hippo/Models/HealthStatus.cs delete mode 100644 src/Hippo/Models/Key.cs delete mode 100644 src/Hippo/Models/Revision.cs delete mode 100644 src/Hippo/OperationData/ICreateApplicationParameters.cs delete mode 100644 src/Hippo/OperationData/ICreateChannelParameters.cs delete mode 100644 src/Hippo/Program.cs delete mode 100644 src/Hippo/Proxies/ChannelConfigProvider.cs delete mode 100644 src/Hippo/Proxies/IReverseProxy.cs delete mode 100644 src/Hippo/Proxies/IReverseProxyUpdater.cs delete mode 100644 src/Hippo/Proxies/ProxyStartup.cs delete mode 100644 src/Hippo/Proxies/YarpReverseProxy.cs delete mode 100644 src/Hippo/Proxies/appsettings.Development.json delete mode 100644 src/Hippo/Proxies/appsettings.json delete mode 100644 src/Hippo/Repositories/ActionContextCurrentUser.cs delete mode 100644 src/Hippo/Repositories/DbAccountRepository.cs delete mode 100644 src/Hippo/Repositories/DbApplicationRepository.cs delete mode 100644 src/Hippo/Repositories/DbChannelRepository.cs delete mode 100644 src/Hippo/Repositories/DbEventLogRepository.cs delete mode 100644 src/Hippo/Repositories/DbRevisionRepository.cs delete mode 100644 src/Hippo/Repositories/DbUnitOfWork.cs delete mode 100644 src/Hippo/Repositories/IAccountRepository.cs delete mode 100644 src/Hippo/Repositories/IApplicationRepository.cs delete mode 100644 src/Hippo/Repositories/IChannelRepository.cs delete mode 100644 src/Hippo/Repositories/ICurrentUser.cs delete mode 100644 src/Hippo/Repositories/IEventLogRepository.cs delete mode 100644 src/Hippo/Repositories/IRevisionRepository.cs delete mode 100644 src/Hippo/Repositories/IUnitOfWork.cs delete mode 100644 src/Hippo/Schedulers/JobScheduler.cs delete mode 100644 src/Hippo/Schedulers/WagiDotnetJobScheduler.cs delete mode 100644 src/Hippo/Tasks/ChannelUpdateBackgroundService.cs delete mode 100644 src/Hippo/Tasks/ReverseProxyUpdateBackgroundService.cs delete mode 100644 src/Hippo/ViewModels/AccountRegisterForm.cs delete mode 100644 src/Hippo/ViewModels/ApiLoginForm.cs delete mode 100644 src/Hippo/ViewModels/AppDetails.cs delete mode 100644 src/Hippo/ViewModels/AppEditChannelForm.cs delete mode 100644 src/Hippo/ViewModels/AppEditForm.cs delete mode 100644 src/Hippo/ViewModels/AppNewChannelForm.cs delete mode 100644 src/Hippo/ViewModels/AppNewForm.cs delete mode 100644 src/Hippo/ViewModels/AppRegisterRevisionForm.cs delete mode 100644 src/Hippo/ViewModels/Converters.cs delete mode 100644 src/Hippo/ViewModels/LoginForm.cs delete mode 100644 src/Hippo/ViewModels/RevisionRegistrationForm.cs delete mode 100644 src/Hippo/Views/Admin/Terminal.cshtml delete mode 100644 src/Hippo/Views/App/Details.cshtml delete mode 100644 src/Hippo/Views/App/NewChannel.cshtml delete mode 100644 src/Hippo/Views/Home/Index.cshtml delete mode 100644 src/Hippo/Views/Shared/_TerminalScriptsPartial.cshtml delete mode 100644 src/Hippo/Views/Shared/_TerminalStylesPartial.cshtml delete mode 100644 src/Hippo/Views/_ViewImports.cshtml delete mode 100644 src/Hippo/WagiDotnet/WagiDotnetStartup.cs delete mode 100644 src/Hippo/WagiDotnet/appsettings.Development.json delete mode 100644 src/Hippo/WagiDotnet/appsettings.json delete mode 100644 src/Hippo/appsettings.Development.json create mode 100644 src/Infrastructure/Data/ApplicationDbContext.cs create mode 100644 src/Infrastructure/Data/ApplicationDbContextSeed.cs create mode 100644 src/Infrastructure/Data/Configurations/AppConfiguration.cs create mode 100644 src/Infrastructure/Data/Configurations/ChannelConfiguration.cs create mode 100644 src/Infrastructure/Data/Configurations/DomainConfiguration.cs create mode 100644 src/Infrastructure/Data/Configurations/EnvironmentVariableConfiguration.cs create mode 100644 src/Infrastructure/Data/Configurations/RevisionConfiguration.cs rename src/{Hippo/Migrations/Sqlite/20210706011225_Initial.Designer.cs => Infrastructure/Data/Migrations/20211210182727_Initial.Designer.cs} (60%) rename src/{Hippo/Migrations/Sqlite/20210706011225_Initial.cs => Infrastructure/Data/Migrations/20211210182727_Initial.cs} (63%) rename src/{Hippo/Migrations/Sqlite/20210722051132_Timestamp.Designer.cs => Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs} (56%) create mode 100644 src/Infrastructure/DependencyInjection.cs create mode 100644 src/Infrastructure/Exceptions/InvalidDatabaseDriverException.cs create mode 100644 src/Infrastructure/Files/JsonFileBuilder.cs create mode 100644 src/Infrastructure/Identity/Account.cs create mode 100644 src/Infrastructure/Identity/IdentityResultExtensions.cs create mode 100644 src/Infrastructure/Identity/IdentityService.cs create mode 100644 src/Infrastructure/Identity/SignInResultExtensions.cs create mode 100644 src/Infrastructure/Identity/SignInService.cs create mode 100644 src/Infrastructure/Infrastructure.csproj rename src/{Hippo/Schedulers/WagiLocalJobScheduler.cs => Infrastructure/JobSchedulers/LocalJobScheduler.cs} (70%) create mode 100644 src/Infrastructure/ReverseProxies/Configuration/InMemoryConfigProvider.cs create mode 100644 src/Infrastructure/ReverseProxies/YarpReverseProxy.cs create mode 100644 src/Infrastructure/Services/DateTimeService.cs create mode 100644 src/Infrastructure/Services/DomainEventService.cs create mode 100644 src/Infrastructure/Services/TokenService.cs rename src/{Hippo => Infrastructure}/Tasks/TaskQueue.cs (74%) rename src/{Hippo => Web}/.vscode/launch.json (100%) rename src/{Hippo => Web}/.vscode/tasks.json (99%) create mode 100644 src/Web/Api/AccountController.cs create mode 100644 src/Web/Api/ApiControllerBase.cs create mode 100644 src/Web/Api/AppController.cs create mode 100644 src/Web/Api/ChannelController.cs create mode 100644 src/Web/Api/DomainController.cs create mode 100644 src/Web/Api/EnvironmentVariableController.cs create mode 100644 src/Web/Api/RevisionController.cs create mode 100644 src/Web/Controllers/AccountController.cs create mode 100644 src/Web/Controllers/AppController.cs rename src/{Hippo => Web}/Controllers/StyleguideController.cs (57%) create mode 100644 src/Web/Controllers/WebUIControllerBase.cs create mode 100644 src/Web/Program.cs rename src/{Hippo => Web}/Properties/launchSettings.json (90%) create mode 100644 src/Web/Services/CurrentUserService.cs rename src/{Hippo => Web}/Views/Account/Index.cshtml (100%) rename src/{Hippo => Web}/Views/Account/Login.cshtml (97%) rename src/{Hippo => Web}/Views/Account/Register.cshtml (83%) rename src/{Hippo => Web}/Views/App/Delete.cshtml (97%) rename src/{Hippo => Web}/Views/App/DeleteChannel.cshtml (94%) create mode 100644 src/Web/Views/App/Details.cshtml rename src/{Hippo => Web}/Views/App/Edit.cshtml (80%) rename src/{Hippo => Web}/Views/App/EditChannel.cshtml (50%) rename src/{Hippo => Web}/Views/App/Index.cshtml (92%) rename src/{Hippo => Web}/Views/App/New.cshtml (97%) create mode 100644 src/Web/Views/App/NewChannel.cshtml rename src/{Hippo => Web}/Views/App/RegisterRevision.cshtml (87%) rename src/{Hippo => Web}/Views/Shared/_AppLayout.cshtml (100%) rename src/{Hippo => Web}/Views/Shared/_Footer.cshtml (100%) rename src/{Hippo => Web}/Views/Shared/_Layout.cshtml (80%) rename src/{Hippo => Web}/Views/Shared/_NavAppTopbar.cshtml (96%) rename src/{Hippo => Web}/Views/Shared/_NavBreadcrumb.cshtml (100%) rename src/{Hippo => Web}/Views/Shared/_NavSidebar.cshtml (78%) rename src/{Hippo => Web}/Views/Shared/_NavTopbar.cshtml (91%) rename src/{Hippo => Web}/Views/Shared/_ValidationScriptsPartial.cshtml (100%) rename src/{Hippo => Web}/Views/Styleguide/Index.cshtml (100%) create mode 100644 src/Web/Views/_ViewImports.cshtml rename src/{Hippo => Web}/Views/_ViewStart.cshtml (100%) create mode 100644 src/Web/Web.csproj create mode 100644 src/Web/appsettings.Development.json rename src/{Hippo => Web}/appsettings.json (84%) rename src/{Hippo => Web}/assets/js/app.js (95%) rename src/{Hippo => Web}/assets/js/dist/app.dev.js (95%) rename src/{Hippo => Web}/assets/styles/dist/hippo.css (100%) rename src/{Hippo => Web}/assets/styles/hippo.scss (100%) rename src/{Hippo => Web}/gulpfile.js (100%) rename src/{Hippo => Web}/nuget.config (91%) rename src/{Hippo => Web}/package-lock.json (99%) rename src/{Hippo => Web}/package.json (100%) create mode 100644 tests/Application.UnitTests/Application.UnitTests.csproj create mode 100644 tests/Application.UnitTests/Common/Behaviours/RequestLoggerTests.cs create mode 100644 tests/Application.UnitTests/Common/Exceptions/ValidationExceptionTests.cs create mode 100644 tests/Application.UnitTests/Common/Mappings/MappingTests.cs create mode 100644 tests/Core.UnitTests/Common/AuditableEntityTests.cs create mode 100644 tests/Core.UnitTests/Common/DomainEventTests.cs create mode 100644 tests/Core.UnitTests/Core.UnitTests.csproj create mode 100644 tests/Core.UnitTests/Enums/ChannelRevisionSelectionStrategyTests.cs create mode 100644 tests/Core.UnitTests/Events/EventTests.cs create mode 100644 tests/Core.UnitTests/Exceptions/ExceptionTests.cs create mode 100644 tests/Hippo.FunctionalTests/Application/Accounts/Commands/CreateAccountTests.cs delete mode 100644 tests/Hippo.FunctionalTests/Controllers/AccountController.cs delete mode 100644 tests/Hippo.FunctionalTests/Fakes/FakeCurrentUser.cs delete mode 100644 tests/Hippo.FunctionalTests/Fakes/FakeSignInManager.cs delete mode 100644 tests/Hippo.FunctionalTests/Fakes/FakeUserManager.cs delete mode 100644 tests/Hippo.FunctionalTests/Fakes/NullLogger.cs create mode 100644 tests/Hippo.FunctionalTests/TestBase.cs create mode 100644 tests/Hippo.FunctionalTests/appsettings.json delete mode 100644 tests/Hippo.UnitTests/Hippo.UnitTests.csproj delete mode 100644 tests/Hippo.UnitTests/Models/ApplicationTest.cs delete mode 100644 tests/Hippo.UnitTests/Models/ChannelTest.cs delete mode 100644 tests/Hippo.UnitTests/Rules/RevisionRangeRuleTests.cs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 58e9d6564..e24a7bc4d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,7 @@ ], "containerEnv": { "BINDLE_URL": "http://localhost:8080/v1", - "HIPPO_URL": "https://localhost:5001", + "HIPPO_URL": "https://localhost:5309", "GLOBAL_AGENT_FORCE_GLOBAL_AGENT": "false", // This places bindle server data in the workspace so that state is retained across multiple invocations of the bindle server // Delete this folder and Hippo/hippo.db.* files to reset Hippo @@ -40,7 +40,7 @@ // restore and build the application, add the dev cert. "postCreateCommand": "cd Hippo && dotnet restore && npm run build && dotnet build && dotnet dev-certs https", "portsAttributes": { - "5001": { + "5309": { "label": "Hippo HTTPS Port", "protocol": "https" }, @@ -61,4 +61,4 @@ "protocol": "http" } } -} \ No newline at end of file +} diff --git a/.gitattributes b/.gitattributes index 5dc46e6b3..314766e91 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ * text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf -*.{bat,[bB][aA][tT]} text eol=crlf \ No newline at end of file +*.{bat,[bB][aA][tT]} text eol=crlf diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bcab6fb3e..af48f7681 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,4 +26,4 @@ updates: time: "08:00" ignore: - dependency-name: "*" - update-types: ["version-update:semver-patch"] \ No newline at end of file + update-types: ["version-update:semver-patch"] diff --git a/.github/release-image/Dockerfile b/.github/release-image/Dockerfile index 7387fb513..1da85417a 100644 --- a/.github/release-image/Dockerfile +++ b/.github/release-image/Dockerfile @@ -71,22 +71,15 @@ ENV GLOBAL_AGENT_FORCE_GLOBAL_AGENT ${FORCE_GLOBAL_AGENT} # Docker/WSL2 has an issue exposing ports from localhost. the following tries to work around this issue. # See https://github.com/microsoft/WSL/issues/4983 - -ARG HIPPO_HTTP_PORT="5000" -ARG HIPPO_HTTPS_PORT="5001" +ARG HIPPO_HTTP_PORT="5308" +ARG HIPPO_HTTPS_PORT="5309" ARG HIPPOURL=https://localhost:${HIPPO_HTTPS_PORT} ENV HIPPO_URL ${HIPPOURL} -ARG YARP_HTTP_PORT="5002" -ARG YARP_HTTPS_PORT="5003" - ENV ASPNETCORE_URLS http://0.0.0.0:${HIPPO_HTTP_PORT};https://0.0.0.0:${HIPPO_HTTPS_PORT} -ENV HIPPO_REVERSE_PROXY_KESTREL__ENDPOINTS__HTTP__URL http://0.0.0.0:${YARP_HTTP_PORT} -ENV HIPPO_REVERSE_PROXY_KESTREL__ENDPOINTS__HTTPS__URL https://0.0.0.0:${YARP_HTTPS_PORT} # Generate certs for hippo-server and proxy - WORKDIR /data/certs RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config /hippo/certs/localhost.conf && openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt -password pass: && sudo chgrp ${USER} localhost.pfx && chmod g+r localhost.pfx RUN cp /data/certs/localhost.crt /usr/local/share/ca-certificates && update-ca-certificates @@ -94,7 +87,6 @@ ENV KESTREL__CERTIFICATES__DEFAULT__PATH /data/certs/localhost.pfx ENV HIPPO_REVERSE_PROXY_KESTREL__CERTIFICATES__DEFAULT__PATH /data/certs/localhost.pfx # Set the data source to a local folder that can be mounted from the host. - ENV CONNECTIONSTRINGS__HIPPO Data Source=/data/hippo/hippo.db;Cache=Shared WORKDIR /hippo @@ -103,4 +95,4 @@ WORKDIR /hippo USER 1000 -ENTRYPOINT if [ -z ${BINDLE_PASSWORD} ]; then export BINDLE_PASSWORD=$(openssl rand -base64 12);echo export BINDLE_PASSWORD=${BINDLE_PASSWORD} >> ~/.bashrc; fi && echo ${BINDLE_PASSWORD}|htpasswd -Bic /data/bindleserver/bindle-htpasswd ${BINDLE_USERNAME} && RUST_LOG=info bindle-server -i ${BINDLE_LISTEN_ADDRESS} --htpasswd-file /data/bindleserver/bindle-htpasswd -d /data/bindleserver >> /data/logs/bindle-server.log 2>&1 & ./hippo-server >> /data/logs/hippo-server.log 2>&1 \ No newline at end of file +ENTRYPOINT if [ -z ${BINDLE_PASSWORD} ]; then export BINDLE_PASSWORD=$(openssl rand -base64 12);echo export BINDLE_PASSWORD=${BINDLE_PASSWORD} >> ~/.bashrc; fi && echo ${BINDLE_PASSWORD}|htpasswd -Bic /data/bindleserver/bindle-htpasswd ${BINDLE_USERNAME} && RUST_LOG=info bindle-server -i ${BINDLE_LISTEN_ADDRESS} --htpasswd-file /data/bindleserver/bindle-htpasswd -d /data/bindleserver >> /data/logs/bindle-server.log 2>&1 & ./hippo-server >> /data/logs/hippo-server.log 2>&1 diff --git a/.github/release-image/localhost.conf b/.github/release-image/localhost.conf index cd5bb37ec..e1bf49d5b 100644 --- a/.github/release-image/localhost.conf +++ b/.github/release-image/localhost.conf @@ -25,4 +25,4 @@ keyUsage = keyCertSign, cRLSign, digitalSignature,keyEncipherment DNS.1 = localhost DNS.2 = 127.0.0.1 DNS.3 = *.localhost -DNS.4 = *.hippofactory.io \ No newline at end of file +DNS.4 = *.hippofactory.io diff --git a/.github/workflows/configuration.json b/.github/workflows/configuration.json index 7d6846fc3..661e6afe9 100644 --- a/.github/workflows/configuration.json +++ b/.github/workflows/configuration.json @@ -31,4 +31,4 @@ "labels": [] } ] - } \ No newline at end of file + } diff --git a/.gitignore b/.gitignore index afb228a11..a4b41663c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # Artifacts -hippo.db* +*.db* # User-specific files *.rsuser diff --git a/.vscode/launch.json b/.vscode/launch.json index ae4e677c3..1086c51ca 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Hippo/bin/Debug/net6.0/hippo-server.dll", + "program": "${workspaceFolder}/src/Web/bin/Debug/net6.0/Hippo.Web.dll", "args": [], - "cwd": "${workspaceFolder}/Hippo", + "cwd": "${workspaceFolder}/src/Web", "stopAtEntry": false, // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 77a4e06e8..0649bd4f3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/Hippo/Hippo.csproj", + "${workspaceFolder}/src/Web/Web.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -19,7 +19,7 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/Hippo/Hippo.csproj", + "${workspaceFolder}/src/Web/Web.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -32,11 +32,11 @@ "args": [ "watch", "run", - "${workspaceFolder}/Hippo/Hippo.csproj", + "${workspaceFolder}/src/Web/Web.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" } ] -} \ No newline at end of file +} diff --git a/Hippo.sln b/Hippo.sln index 9fe106216..932ac1f86 100644 --- a/Hippo.sln +++ b/Hippo.sln @@ -3,15 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30114.105 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CAA698E8-2A2C-4523-AC31-1513B0AB3633}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A432DA6D-ADCE-4EA1-9697-7A637F75605D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hippo", "src\Hippo\Hippo.csproj", "{1E2837B6-0B9D-40C1-A0DB-0416B89BF929}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "src\Application\Application.csproj", "{96764F9E-66BB-4F64-A1BA-DE92537996E1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{4AA8306D-E862-4CC2-8F8C-1A7E1B714201}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{1FD50DE9-0E1C-413D-B034-0266835574C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hippo.FunctionalTests", "tests\Hippo.FunctionalTests\Hippo.FunctionalTests.csproj", "{CDCBB1D6-880E-4C43-891B-C7DBB4A50654}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{DBD7AE2C-2405-45D5-AEE5-CF46D3200138}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hippo.UnitTests", "tests\Hippo.UnitTests\Hippo.UnitTests.csproj", "{3CA6E316-4C70-4123-96A4-B82EFC269396}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{AE9D2078-6A9E-4451-A3E6-79491749902E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3043D016-B9BF-4706-9773-3D58457A78AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.UnitTests", "tests\Application.UnitTests\Application.UnitTests.csproj", "{AE958DF1-B480-4556-A68C-E042072DB7A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.UnitTests", "tests\Core.UnitTests\Core.UnitTests.csproj", "{0BFC9718-3107-4E48-B952-C0B345A55A20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hippo.FunctionalTests", "tests\Hippo.FunctionalTests\Hippo.FunctionalTests.csproj", "{8C25A909-52B2-4326-BF00-23C053376EB5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,22 +30,42 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1E2837B6-0B9D-40C1-A0DB-0416B89BF929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E2837B6-0B9D-40C1-A0DB-0416B89BF929}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E2837B6-0B9D-40C1-A0DB-0416B89BF929}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E2837B6-0B9D-40C1-A0DB-0416B89BF929}.Release|Any CPU.Build.0 = Release|Any CPU - {CDCBB1D6-880E-4C43-891B-C7DBB4A50654}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDCBB1D6-880E-4C43-891B-C7DBB4A50654}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDCBB1D6-880E-4C43-891B-C7DBB4A50654}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDCBB1D6-880E-4C43-891B-C7DBB4A50654}.Release|Any CPU.Build.0 = Release|Any CPU - {3CA6E316-4C70-4123-96A4-B82EFC269396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CA6E316-4C70-4123-96A4-B82EFC269396}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CA6E316-4C70-4123-96A4-B82EFC269396}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CA6E316-4C70-4123-96A4-B82EFC269396}.Release|Any CPU.Build.0 = Release|Any CPU + {96764F9E-66BB-4F64-A1BA-DE92537996E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96764F9E-66BB-4F64-A1BA-DE92537996E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96764F9E-66BB-4F64-A1BA-DE92537996E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96764F9E-66BB-4F64-A1BA-DE92537996E1}.Release|Any CPU.Build.0 = Release|Any CPU + {1FD50DE9-0E1C-413D-B034-0266835574C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FD50DE9-0E1C-413D-B034-0266835574C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FD50DE9-0E1C-413D-B034-0266835574C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FD50DE9-0E1C-413D-B034-0266835574C6}.Release|Any CPU.Build.0 = Release|Any CPU + {DBD7AE2C-2405-45D5-AEE5-CF46D3200138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBD7AE2C-2405-45D5-AEE5-CF46D3200138}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBD7AE2C-2405-45D5-AEE5-CF46D3200138}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBD7AE2C-2405-45D5-AEE5-CF46D3200138}.Release|Any CPU.Build.0 = Release|Any CPU + {AE9D2078-6A9E-4451-A3E6-79491749902E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE9D2078-6A9E-4451-A3E6-79491749902E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE9D2078-6A9E-4451-A3E6-79491749902E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE9D2078-6A9E-4451-A3E6-79491749902E}.Release|Any CPU.Build.0 = Release|Any CPU + {AE958DF1-B480-4556-A68C-E042072DB7A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE958DF1-B480-4556-A68C-E042072DB7A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE958DF1-B480-4556-A68C-E042072DB7A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE958DF1-B480-4556-A68C-E042072DB7A6}.Release|Any CPU.Build.0 = Release|Any CPU + {0BFC9718-3107-4E48-B952-C0B345A55A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BFC9718-3107-4E48-B952-C0B345A55A20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BFC9718-3107-4E48-B952-C0B345A55A20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BFC9718-3107-4E48-B952-C0B345A55A20}.Release|Any CPU.Build.0 = Release|Any CPU + {8C25A909-52B2-4326-BF00-23C053376EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C25A909-52B2-4326-BF00-23C053376EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C25A909-52B2-4326-BF00-23C053376EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C25A909-52B2-4326-BF00-23C053376EB5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {1E2837B6-0B9D-40C1-A0DB-0416B89BF929} = {CAA698E8-2A2C-4523-AC31-1513B0AB3633} - {CDCBB1D6-880E-4C43-891B-C7DBB4A50654} = {4AA8306D-E862-4CC2-8F8C-1A7E1B714201} - {3CA6E316-4C70-4123-96A4-B82EFC269396} = {4AA8306D-E862-4CC2-8F8C-1A7E1B714201} + {96764F9E-66BB-4F64-A1BA-DE92537996E1} = {A432DA6D-ADCE-4EA1-9697-7A637F75605D} + {1FD50DE9-0E1C-413D-B034-0266835574C6} = {A432DA6D-ADCE-4EA1-9697-7A637F75605D} + {DBD7AE2C-2405-45D5-AEE5-CF46D3200138} = {A432DA6D-ADCE-4EA1-9697-7A637F75605D} + {AE9D2078-6A9E-4451-A3E6-79491749902E} = {A432DA6D-ADCE-4EA1-9697-7A637F75605D} + {AE958DF1-B480-4556-A68C-E042072DB7A6} = {3043D016-B9BF-4706-9773-3D58457A78AC} + {0BFC9718-3107-4E48-B952-C0B345A55A20} = {3043D016-B9BF-4706-9773-3D58457A78AC} + {8C25A909-52B2-4326-BF00-23C053376EB5} = {3043D016-B9BF-4706-9773-3D58457A78AC} EndGlobalSection EndGlobal diff --git a/scripts/build-image.sh b/scripts/build-image.sh index bd9732210..35b148692 100755 --- a/scripts/build-image.sh +++ b/scripts/build-image.sh @@ -14,6 +14,6 @@ dotnet publish Hippo/Hippo.csproj -c Release --self-contained -r linux-x64 cd $ROOT_DIR/.github/release-image -mkdir -p $ROOT_DIR/Hippo/bin/Release/net5.0/linux-x64/publish/certs -cp localhost.conf $ROOT_DIR/Hippo/bin/Release/net5.0/linux-x64/publish/certs -docker build -t hippo -f $ROOT_DIR/.github/release-image/Dockerfile $ROOT_DIR/Hippo/bin/Release/net5.0/linux-x64/publish \ No newline at end of file +mkdir -p $ROOT_DIR/src/Web/bin/Release/net6.0/linux-x64/publish/certs +cp localhost.conf $ROOT_DIR/src/Web/bin/Release/net6.0/linux-x64/publish/certs +docker build -t hippo -f $ROOT_DIR/.github/release-image/Dockerfile $ROOT_DIR/src/Web/bin/Release/net6.0/linux-x64/publish diff --git a/src/Application/Accounts/Commands/CreateAccountCommand.cs b/src/Application/Accounts/Commands/CreateAccountCommand.cs new file mode 100644 index 000000000..dfb27b8cb --- /dev/null +++ b/src/Application/Accounts/Commands/CreateAccountCommand.cs @@ -0,0 +1,33 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Accounts.Commands; + +public class CreateAccountCommand : IRequest +{ + public string? UserName { get; set; } + + public string? Password { get; set; } + + public string? PasswordConfirm { get; set; } +} + +public class CreateAccountCommandHandler : IRequestHandler +{ + private readonly IIdentityService _identityService; + + public CreateAccountCommandHandler(IIdentityService identityService) + { + _identityService = identityService; + } + + public async Task Handle(CreateAccountCommand request, CancellationToken cancellationToken) + { + var result = await _identityService.CreateUserAsync(request.UserName!, request.Password!); + + return result.UserId; + } +} diff --git a/src/Application/Accounts/Commands/CreateAccountCommandValidator.cs b/src/Application/Accounts/Commands/CreateAccountCommandValidator.cs new file mode 100644 index 000000000..a87992d68 --- /dev/null +++ b/src/Application/Accounts/Commands/CreateAccountCommandValidator.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using Hippo.Application.Common.Interfaces; + +namespace Hippo.Application.Accounts.Commands; + +public class CreateAccountCommandValidator : AbstractValidator +{ + private readonly IIdentityService _identityService; + public CreateAccountCommandValidator(IIdentityService identityService) + { + _identityService = identityService; + + RuleFor(a => a.UserName) + .MaximumLength(32).WithMessage("username must not exceed 32 characters.") + .MustAsync(BeUniqueUserName).WithMessage("username already exists.") + .NotEmpty(); + + RuleFor(a => a.Password) + .MinimumLength(8) + .NotEmpty(); + + RuleFor(a => a.PasswordConfirm) + .Equal(a => a.Password).WithMessage("Passwords do not match"); + } + + public async Task BeUniqueUserName(string userName, CancellationToken cancellationToken) + { + try + { + await _identityService.GetUserIdAsync(userName); + return false; + } + catch (Exception) + { + return true; + } + } +} diff --git a/src/Application/Accounts/Commands/CreateTokenCommand.cs b/src/Application/Accounts/Commands/CreateTokenCommand.cs new file mode 100644 index 000000000..2eb711fa4 --- /dev/null +++ b/src/Application/Accounts/Commands/CreateTokenCommand.cs @@ -0,0 +1,42 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Accounts.Commands; + +public class CreateTokenCommand : IRequest +{ + public string UserName { get; set; } + + public string Password { get; set; } + + public CreateTokenCommand(string username, string password) + { + UserName = username; + Password = password; + } +} + +public class CreateTokenCommandHandler : IRequestHandler +{ + private readonly IIdentityService _identityService; + private readonly ITokenService _tokenService; + + public CreateTokenCommandHandler(IIdentityService identityService, ITokenService tokenService) + { + _identityService = identityService; + _tokenService = tokenService; + } + + public async Task Handle(CreateTokenCommand request, CancellationToken cancellationToken) + { + if (await _identityService.CheckPasswordAsync(request.UserName, request.Password)) + { + return _tokenService.CreateSecurityToken(request.UserName); + } + + throw new LoginFailedException(); + } +} diff --git a/src/Application/Accounts/Commands/LoginAccountCommand.cs b/src/Application/Accounts/Commands/LoginAccountCommand.cs new file mode 100644 index 000000000..168216c6b --- /dev/null +++ b/src/Application/Accounts/Commands/LoginAccountCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Accounts.Commands; + +public class LoginAccountCommand : IRequest +{ + public string? UserName { get; set; } + + public string? Password { get; set; } + + public bool RememberMe { get; set; } +} + +public class LoginAccountCommandHandler : IRequestHandler +{ + private readonly IIdentityService _identityService; + private readonly ISignInService _signInService; + + public LoginAccountCommandHandler(IIdentityService identityService, ISignInService signInService) + { + _identityService = identityService; + _signInService = signInService; + } + + public async Task Handle(LoginAccountCommand request, CancellationToken cancellationToken) + { + var result = await _signInService.PasswordSignInAsync(request.UserName!, request.Password!, request.RememberMe); + + if (!result.Succeeded) + { + throw new LoginFailedException(result.Errors); + } + + return Unit.Value; + } +} diff --git a/src/Application/Accounts/Commands/LogoutAccountCommand.cs b/src/Application/Accounts/Commands/LogoutAccountCommand.cs new file mode 100644 index 000000000..fc3291891 --- /dev/null +++ b/src/Application/Accounts/Commands/LogoutAccountCommand.cs @@ -0,0 +1,26 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Accounts.Commands; + +public class LogoutAccountCommand : IRequest +{ +} + +public class LogoutAccountCommandHandler : IRequestHandler +{ + private readonly ISignInService _signInService; + + public LogoutAccountCommandHandler(ISignInService signInService) + { + _signInService = signInService; + } + + public async Task Handle(LogoutAccountCommand request, CancellationToken cancellationToken) + { + return await _signInService.SignOutAsync(); + } +} diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj new file mode 100644 index 000000000..6157fdc7b --- /dev/null +++ b/src/Application/Application.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + Hippo.Application + Hippo.Application + + + + + + + + + + + + + + + diff --git a/src/Application/Apps/Commands/CreateAppCommand.cs b/src/Application/Apps/Commands/CreateAppCommand.cs new file mode 100644 index 000000000..79b0ecab7 --- /dev/null +++ b/src/Application/Apps/Commands/CreateAppCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Apps.Commands; + +public class CreateAppCommand : IRequest +{ + public string? Name { get; set; } + + public string? StorageId { get; set; } +} + +public class CreateAppCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateAppCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateAppCommand request, CancellationToken cancellationToken) + { + var entity = new App + { + Name = request.Name, + StorageId = request.StorageId + }; + + entity.DomainEvents.Add(new AppCreatedEvent(entity)); + + _context.Apps.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/Application/Apps/Commands/CreateAppCommandValidator.cs b/src/Application/Apps/Commands/CreateAppCommandValidator.cs new file mode 100644 index 000000000..8d8edbcb2 --- /dev/null +++ b/src/Application/Apps/Commands/CreateAppCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace Hippo.Application.Apps.Commands; + +public class CreateAppCommandValidator : AbstractValidator +{ + public CreateAppCommandValidator() + { + RuleFor(v => v.Name) + .MaximumLength(32) + .NotEmpty(); + + RuleFor(v => v.StorageId) + .NotEmpty(); + } +} diff --git a/src/Application/Apps/Commands/DeleteAppCommand.cs b/src/Application/Apps/Commands/DeleteAppCommand.cs new file mode 100644 index 000000000..d82d68d9b --- /dev/null +++ b/src/Application/Apps/Commands/DeleteAppCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Apps.Commands; + +public class DeleteAppCommand : IRequest +{ + public Guid Id { get; set; } +} + +public class DeleteAppCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteAppCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteAppCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Apps + .Where(l => l.Id == request.Id) + .SingleOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(App), request.Id); + } + + _context.Apps.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Apps/Commands/PurgeAppsCommand.cs b/src/Application/Apps/Commands/PurgeAppsCommand.cs new file mode 100644 index 000000000..f2b147007 --- /dev/null +++ b/src/Application/Apps/Commands/PurgeAppsCommand.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.Apps.Commands; + +[Authorize(Roles = "Administrator")] +[Authorize(Policy = "CanPurge")] +public class PurgeAppsCommand : IRequest +{ +} + +public class PurgeAppsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public PurgeAppsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(PurgeAppsCommand request, CancellationToken cancellationToken) + { + _context.Apps.RemoveRange(_context.Apps); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Apps/Commands/UpdateAppCommand.cs b/src/Application/Apps/Commands/UpdateAppCommand.cs new file mode 100644 index 000000000..3b22c666d --- /dev/null +++ b/src/Application/Apps/Commands/UpdateAppCommand.cs @@ -0,0 +1,44 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Apps.Commands; + +public class UpdateAppCommand : IRequest +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public string? StorageId { get; set; } +} + +public class UpdateAppCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateAppCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateAppCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Apps + .FindAsync(new object[] { request.Id }, cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(App), request.Id); + } + + entity.Name = request.Name; + entity.StorageId = request.StorageId; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Apps/Commands/UpdateAppCommandValidator.cs b/src/Application/Apps/Commands/UpdateAppCommandValidator.cs new file mode 100644 index 000000000..f01e8b5f0 --- /dev/null +++ b/src/Application/Apps/Commands/UpdateAppCommandValidator.cs @@ -0,0 +1,29 @@ +using Hippo.Application.Common.Interfaces; +using FluentValidation; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Apps.Commands; + +public class UpdateAppCommandValidator : AbstractValidator +{ + private readonly IApplicationDbContext _context; + public UpdateAppCommandValidator(IApplicationDbContext context) + { + _context = context; + + RuleFor(v => v.Name) + .NotEmpty().WithMessage("Name is required.") + .MaximumLength(32).WithMessage("Name must not exceed 32 characters.") + .MustAsync(BeUniqueName).WithMessage("The specified name already exists."); + + RuleFor(v => v.StorageId) + .NotEmpty(); + } + + public async Task BeUniqueName(UpdateAppCommand model, string name, CancellationToken cancellationToken) + { + return await _context.Apps + .Where(l => l.Id != model.Id) + .AllAsync(l => l.Name != name, cancellationToken); + } +} diff --git a/src/Application/Apps/EventHandlers/AppCreatedEventHandler.cs b/src/Application/Apps/EventHandlers/AppCreatedEventHandler.cs new file mode 100644 index 000000000..2b56481da --- /dev/null +++ b/src/Application/Apps/EventHandlers/AppCreatedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Apps.EventHandlers; + +public class AppCreatedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public AppCreatedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Apps/EventHandlers/AppDeletedEventHandler.cs b/src/Application/Apps/EventHandlers/AppDeletedEventHandler.cs new file mode 100644 index 000000000..de07f1fb3 --- /dev/null +++ b/src/Application/Apps/EventHandlers/AppDeletedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Apps.EventHandlers; + +public class AppDeletedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public AppDeletedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Apps/Queries/AppDto.cs b/src/Application/Apps/Queries/AppDto.cs new file mode 100644 index 000000000..c1431a716 --- /dev/null +++ b/src/Application/Apps/Queries/AppDto.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Channels.Queries; +using Hippo.Application.Common.Mappings; +using Hippo.Application.Revisions.Queries; +using Hippo.Core.Entities; + +namespace Hippo.Application.Apps.Queries; + +public class AppDto : IMapFrom +{ + public AppDto() + { + Channels = new List(); + Revisions = new List(); + } + + public Guid Id { get; set; } + + public string? Name { get; set; } + + public string? StorageId { get; set; } + + public IList Channels { get; set; } + + public IList Revisions { get; set; } +} diff --git a/src/Application/Apps/Queries/AppRecord.cs b/src/Application/Apps/Queries/AppRecord.cs new file mode 100644 index 000000000..03f82c203 --- /dev/null +++ b/src/Application/Apps/Queries/AppRecord.cs @@ -0,0 +1,23 @@ +using Hippo.Application.Channels.Queries; +using Hippo.Application.Common.Mappings; +using Hippo.Application.Revisions.Queries; +using Hippo.Core.Entities; + +namespace Hippo.Application.Apps.Queries; + +public class AppRecord : IMapFrom +{ + public AppRecord() + { + Channels = new List(); + Revisions = new List(); + } + + public string? Name { get; set; } + + public string? StorageId { get; set; } + + public IList Channels { get; set; } + + public IList Revisions { get; set; } +} diff --git a/src/Application/Apps/Queries/AppsVm.cs b/src/Application/Apps/Queries/AppsVm.cs new file mode 100644 index 000000000..960f53e15 --- /dev/null +++ b/src/Application/Apps/Queries/AppsVm.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Apps.Queries; + +public class AppsVm +{ + public IList Apps { get; set; } = new List(); +} diff --git a/src/Application/Apps/Queries/ExportAppsQuery.cs b/src/Application/Apps/Queries/ExportAppsQuery.cs new file mode 100644 index 000000000..72c5cc5cd --- /dev/null +++ b/src/Application/Apps/Queries/ExportAppsQuery.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Apps.Queries; + +public class ExportAppsQuery : IRequest +{ +} + +public class ExportAppsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IJsonFileBuilder _fileBuilder; + + public ExportAppsQueryHandler(IApplicationDbContext context, IMapper mapper, IJsonFileBuilder fileBuilder) + { + _context = context; + _mapper = mapper; + _fileBuilder = fileBuilder; + } + + public async Task Handle(ExportAppsQuery request, CancellationToken cancellationToken) + { + var records = await _context.Apps + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + var vm = new ExportAppsVm( + "apps.json", + "application/json", + _fileBuilder.BuildAppsFile(records)); + return vm; + } +} diff --git a/src/Application/Apps/Queries/ExportAppsVm.cs b/src/Application/Apps/Queries/ExportAppsVm.cs new file mode 100644 index 000000000..86d504f8d --- /dev/null +++ b/src/Application/Apps/Queries/ExportAppsVm.cs @@ -0,0 +1,17 @@ +namespace Hippo.Application.Apps.Queries; + +public class ExportAppsVm +{ + public ExportAppsVm(string fileName, string contentType, byte[] content) + { + FileName = fileName; + ContentType = contentType; + Content = content; + } + + public string FileName { get; set; } + + public string ContentType { get; set; } + + public byte[] Content { get; set; } +} diff --git a/src/Application/Apps/Queries/GetAppQuery.cs b/src/Application/Apps/Queries/GetAppQuery.cs new file mode 100644 index 000000000..6776f5314 --- /dev/null +++ b/src/Application/Apps/Queries/GetAppQuery.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Apps.Queries; + +public class GetAppQuery : IRequest +{ + public Guid Id { get; set; } +} + +public class GetAppQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetAppQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetAppQuery request, CancellationToken cancellationToken) + { + var entity = await _context.Apps + .Where(a => a.Id == request.Id) + .ProjectTo(_mapper.ConfigurationProvider) + .FirstOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(App), request.Id); + } + + return entity; + } +} diff --git a/src/Application/Apps/Queries/GetAppsQuery.cs b/src/Application/Apps/Queries/GetAppsQuery.cs new file mode 100644 index 000000000..fe052fd3a --- /dev/null +++ b/src/Application/Apps/Queries/GetAppsQuery.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Apps.Queries; + +public class GetAppsQuery : IRequest +{ +} + +public class GetAppsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetAppsQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetAppsQuery request, CancellationToken cancellationToken) + { + return new AppsVm + { + Apps = await _context.Apps + .ProjectTo(_mapper.ConfigurationProvider) + .OrderBy(a => a.Name) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/Application/Channels/Commands/CreateChannelCommand.cs b/src/Application/Channels/Commands/CreateChannelCommand.cs new file mode 100644 index 000000000..ec746d86a --- /dev/null +++ b/src/Application/Channels/Commands/CreateChannelCommand.cs @@ -0,0 +1,51 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Enums; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Channels.Commands; + +public class CreateChannelCommand : IRequest +{ + public Guid AppId { get; set; } + + public string? Name { get; set; } + + public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } + + public string? RangeRule { get; set; } + + public Revision? ActiveRevision { get; set; } +} + +public class CreateChannelCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateChannelCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateChannelCommand request, CancellationToken cancellationToken) + { + var entity = new Channel + { + AppId = request.AppId, + Name = request.Name, + RevisionSelectionStrategy = request.RevisionSelectionStrategy, + RangeRule = request.RangeRule, + ActiveRevision = request.ActiveRevision, + PortId = _context.Channels.Count() + }; + + entity.DomainEvents.Add(new ChannelCreatedEvent(entity)); + + _context.Channels.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/Application/Channels/Commands/CreateChannelCommandValidator.cs b/src/Application/Channels/Commands/CreateChannelCommandValidator.cs new file mode 100644 index 000000000..b0446a4ef --- /dev/null +++ b/src/Application/Channels/Commands/CreateChannelCommandValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; + +namespace Hippo.Application.Channels.Commands; + +public class CreateChannelCommandValidator : AbstractValidator +{ + public CreateChannelCommandValidator() + { + RuleFor(v => v.Name) + .MaximumLength(32) + .NotEmpty(); + } +} diff --git a/src/Application/Channels/Commands/DeleteChannelCommand.cs b/src/Application/Channels/Commands/DeleteChannelCommand.cs new file mode 100644 index 000000000..b4f830ef2 --- /dev/null +++ b/src/Application/Channels/Commands/DeleteChannelCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Channels.Commands; + +public class DeleteChannelCommand : IRequest +{ + public Guid Id { get; set; } +} + +public class DeleteChannelCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteChannelCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteChannelCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Channels + .Where(l => l.Id == request.Id) + .SingleOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(Channel), request.Id); + } + + _context.Channels.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Channels/Commands/PurgeChannelsCommand.cs b/src/Application/Channels/Commands/PurgeChannelsCommand.cs new file mode 100644 index 000000000..8bfcf138c --- /dev/null +++ b/src/Application/Channels/Commands/PurgeChannelsCommand.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.Channels.Commands; + +[Authorize(Roles = "Administrator")] +[Authorize(Policy = "CanPurge")] +public class PurgeChannelsCommand : IRequest +{ +} + +public class PurgeChannelsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public PurgeChannelsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(PurgeChannelsCommand request, CancellationToken cancellationToken) + { + _context.Channels.RemoveRange(_context.Channels); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Channels/Commands/UpdateChannelCommand.cs b/src/Application/Channels/Commands/UpdateChannelCommand.cs new file mode 100644 index 000000000..72866e3e5 --- /dev/null +++ b/src/Application/Channels/Commands/UpdateChannelCommand.cs @@ -0,0 +1,56 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Enums; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Channels.Commands; + +public class UpdateChannelCommand : IRequest +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } + + public string? RangeRule { get; set; } + + public Revision? ActiveRevision { get; set; } +} + +public class UpdateChannelCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateChannelCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateChannelCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Channels + .FindAsync(new object[] { request.Id }, cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(Channel), request.Id); + } + + if (request.ActiveRevision != null && request.ActiveRevision != entity.ActiveRevision) + { + entity.DomainEvents.Add(new ActiveRevisionChangedEvent(entity, request.ActiveRevision)); + } + + entity.Name = request.Name; + entity.RevisionSelectionStrategy = request.RevisionSelectionStrategy; + entity.RangeRule = request.RangeRule; + entity.ActiveRevision = request.ActiveRevision; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs b/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs new file mode 100644 index 000000000..7ec187c2c --- /dev/null +++ b/src/Application/Channels/EventHandlers/ActiveRevisionChangedEventHandler.cs @@ -0,0 +1,42 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Channels.EventHandlers; + +public class ActiveRevisionChangedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + private readonly IJobScheduler _jobScheduler; + + public ActiveRevisionChangedEventHandler(ILogger logger, IJobScheduler jobScheduler) + { + _logger = logger; + _jobScheduler = jobScheduler; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + if (domainEvent.Channel.ActiveRevision != null) + { + _logger.LogInformation($"ExecuteAsync: stopping {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + _jobScheduler.Stop(domainEvent.Channel); + _logger.LogInformation($"ExecuteAsync: starting {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + _jobScheduler.Start(domainEvent.Channel); + _logger.LogInformation($"ExecuteAsync: started {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name} at rev {domainEvent.Channel.ActiveRevision.RevisionNumber}"); + } + else + { + _logger.LogInformation($"ExecuteAsync: not restarting {domainEvent.Channel.App.Name} channel {domainEvent.Channel.Name}: no active revision"); + } + + return Task.CompletedTask; + } +} diff --git a/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs b/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs new file mode 100644 index 000000000..7b28a8571 --- /dev/null +++ b/src/Application/Channels/EventHandlers/ChannelCreatedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Channels.EventHandlers; + +public class ChannelCreatedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public ChannelCreatedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs b/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs new file mode 100644 index 000000000..f7a76a4a1 --- /dev/null +++ b/src/Application/Channels/EventHandlers/ChannelDeletedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Channels.EventHandlers; + +public class ChannelDeletedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public ChannelDeletedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Channels/Queries/ChannelDto.cs b/src/Application/Channels/Queries/ChannelDto.cs new file mode 100644 index 000000000..1bcbae2a9 --- /dev/null +++ b/src/Application/Channels/Queries/ChannelDto.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Application.EnvironmentVariables.Queries; +using Hippo.Core.Entities; +using Hippo.Core.Enums; + +namespace Hippo.Application.Channels.Queries; + +public class ChannelDto : IMapFrom +{ + public ChannelDto() + { + EnvironmentVariables = new List(); + } + + public Guid Id { get; set; } + + public Guid AppId { get; set; } + + public string? Name { get; set; } + + public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } + + public Revision? ActiveRevision { get; set; } + + public string? RangeRule { get; set; } + + public Domain? Domain { get; set; } + + public IList EnvironmentVariables { get; set; } +} diff --git a/src/Application/Channels/Queries/ChannelRecord.cs b/src/Application/Channels/Queries/ChannelRecord.cs new file mode 100644 index 000000000..4f2334b6c --- /dev/null +++ b/src/Application/Channels/Queries/ChannelRecord.cs @@ -0,0 +1,28 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Application.Domains.Queries; +using Hippo.Application.EnvironmentVariables.Queries; +using Hippo.Application.Revisions.Queries; +using Hippo.Core.Entities; +using Hippo.Core.Enums; + +namespace Hippo.Application.Channels.Queries; + +public class ChannelRecord : IMapFrom +{ + public ChannelRecord() + { + EnvironmentVariables = new List(); + } + + public string? Name { get; set; } + + public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } + + public RevisionRecord? ActiveRevision { get; set; } + + public string? RangeRule { get; set; } + + public DomainRecord? Domain { get; set; } + + public IList EnvironmentVariables { get; set; } +} diff --git a/src/Application/Channels/Queries/ChannelsVm.cs b/src/Application/Channels/Queries/ChannelsVm.cs new file mode 100644 index 000000000..f07993d80 --- /dev/null +++ b/src/Application/Channels/Queries/ChannelsVm.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Channels.Queries; + +public class ChannelsVm +{ + public IList Channels { get; set; } = new List(); +} diff --git a/src/Application/Channels/Queries/ExportChannelsQuery.cs b/src/Application/Channels/Queries/ExportChannelsQuery.cs new file mode 100644 index 000000000..716bedc39 --- /dev/null +++ b/src/Application/Channels/Queries/ExportChannelsQuery.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Channels.Queries; + +public class ExportChannelsQuery : IRequest +{ +} + +public class ExportChannelsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IJsonFileBuilder _fileBuilder; + + public ExportChannelsQueryHandler(IApplicationDbContext context, IMapper mapper, IJsonFileBuilder fileBuilder) + { + _context = context; + _mapper = mapper; + _fileBuilder = fileBuilder; + } + + public async Task Handle(ExportChannelsQuery request, CancellationToken cancellationToken) + { + var records = await _context.Channels + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + var vm = new ExportChannelsVm( + "channels.json", + "application/json", + _fileBuilder.BuildChannelsFile(records)); + return vm; + } +} diff --git a/src/Application/Channels/Queries/ExportChannelsVm.cs b/src/Application/Channels/Queries/ExportChannelsVm.cs new file mode 100644 index 000000000..7f4f22b1b --- /dev/null +++ b/src/Application/Channels/Queries/ExportChannelsVm.cs @@ -0,0 +1,17 @@ +namespace Hippo.Application.Channels.Queries; + +public class ExportChannelsVm +{ + public ExportChannelsVm(string fileName, string contentType, byte[] content) + { + FileName = fileName; + ContentType = contentType; + Content = content; + } + + public string FileName { get; set; } + + public string ContentType { get; set; } + + public byte[] Content { get; set; } +} diff --git a/src/Application/Channels/Queries/GetChannelsQuery.cs b/src/Application/Channels/Queries/GetChannelsQuery.cs new file mode 100644 index 000000000..b11b4b5f0 --- /dev/null +++ b/src/Application/Channels/Queries/GetChannelsQuery.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Channels.Queries; + +public class GetChannelsQuery : IRequest +{ +} + +public class GetChannelsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetChannelsQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetChannelsQuery request, CancellationToken cancellationToken) + { + return new ChannelsVm + { + Channels = await _context.Channels + .ProjectTo(_mapper.ConfigurationProvider) + .OrderBy(c => c.Name) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/Application/Common/Behaviours/AuthorizationBehaviour.cs b/src/Application/Common/Behaviours/AuthorizationBehaviour.cs new file mode 100644 index 000000000..e6cc9df8a --- /dev/null +++ b/src/Application/Common/Behaviours/AuthorizationBehaviour.cs @@ -0,0 +1,80 @@ +using System.Reflection; +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.Common.Behaviours; + +public class AuthorizationBehaviour : IPipelineBehavior where TRequest : notnull +{ + private readonly ICurrentUserService _currentUserService; + private readonly IIdentityService _identityService; + + public AuthorizationBehaviour( + ICurrentUserService currentUserService, + IIdentityService identityService) + { + _currentUserService = currentUserService; + _identityService = identityService; + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + var authorizeAttributes = request.GetType().GetCustomAttributes(); + + if (authorizeAttributes.Any()) + { + // Must be authenticated user + if (_currentUserService.UserId == null) + { + throw new UnauthorizedAccessException(); + } + + // Role-based authorization + var authorizeAttributesWithRoles = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Roles)); + + if (authorizeAttributesWithRoles.Any()) + { + var authorized = false; + + foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(','))) + { + foreach (var role in roles) + { + var isInRole = await _identityService.IsInRoleAsync(_currentUserService.UserId, role.Trim()); + if (isInRole) + { + authorized = true; + break; + } + } + } + + // Must be a member of at least one role in roles + if (!authorized) + { + throw new ForbiddenAccessException(); + } + } + + // Policy-based authorization + var authorizeAttributesWithPolicies = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Policy)); + if (authorizeAttributesWithPolicies.Any()) + { + foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy)) + { + var authorized = await _identityService.AuthorizeAsync(_currentUserService.UserId, policy); + + if (!authorized) + { + throw new ForbiddenAccessException(); + } + } + } + } + + // User is authorized / authorization not required + return await next(); + } +} diff --git a/src/Application/Common/Behaviours/LoggingBehaviour.cs b/src/Application/Common/Behaviours/LoggingBehaviour.cs new file mode 100644 index 000000000..7b1284952 --- /dev/null +++ b/src/Application/Common/Behaviours/LoggingBehaviour.cs @@ -0,0 +1,34 @@ +using Hippo.Application.Common.Interfaces; +using MediatR.Pipeline; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Common.Behaviours; + +public class LoggingBehaviour : IRequestPreProcessor where TRequest : notnull +{ + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + private readonly IIdentityService _identityService; + + public LoggingBehaviour(ILogger logger, ICurrentUserService currentUserService, IIdentityService identityService) + { + _logger = logger; + _currentUserService = currentUserService; + _identityService = identityService; + } + + public async Task Process(TRequest request, CancellationToken cancellationToken) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + string userName = string.Empty; + + if (!string.IsNullOrEmpty(userId)) + { + userName = await _identityService.GetUserNameAsync(userId); + } + + _logger.LogInformation("Hippo Request: {Name} {@UserId} {@UserName} {@Request}", + requestName, userId, userName, request); + } +} diff --git a/src/Application/Common/Behaviours/PerformanceBehaviour.cs b/src/Application/Common/Behaviours/PerformanceBehaviour.cs new file mode 100644 index 000000000..09e3e9581 --- /dev/null +++ b/src/Application/Common/Behaviours/PerformanceBehaviour.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Common.Behaviours; + +public class PerformanceBehaviour : IPipelineBehavior where TRequest : notnull +{ + private readonly Stopwatch _timer; + private readonly ILogger _logger; + private readonly ICurrentUserService _currentUserService; + private readonly IIdentityService _identityService; + + public PerformanceBehaviour( + ILogger logger, + ICurrentUserService currentUserService, + IIdentityService identityService) + { + _timer = new Stopwatch(); + + _logger = logger; + _currentUserService = currentUserService; + _identityService = identityService; + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + _timer.Start(); + + var response = await next(); + + _timer.Stop(); + + var elapsedMilliseconds = _timer.ElapsedMilliseconds; + + if (elapsedMilliseconds > 500) + { + var requestName = typeof(TRequest).Name; + var userId = _currentUserService.UserId ?? string.Empty; + var userName = string.Empty; + + if (!string.IsNullOrEmpty(userId)) + { + userName = await _identityService.GetUserNameAsync(userId); + } + + _logger.LogWarning("Hippo Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}", + requestName, elapsedMilliseconds, userId, userName, request); + } + + return response; + } +} diff --git a/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs new file mode 100644 index 000000000..0a422a6c5 --- /dev/null +++ b/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -0,0 +1,30 @@ +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Common.Behaviours; + +public class UnhandledExceptionBehaviour : IPipelineBehavior where TRequest : notnull +{ + private readonly ILogger _logger; + + public UnhandledExceptionBehaviour(ILogger logger) + { + _logger = logger; + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + try + { + return await next(); + } + catch (Exception ex) + { + var requestName = typeof(TRequest).Name; + + _logger.LogError(ex, "Hippo Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + + throw; + } + } +} diff --git a/src/Application/Common/Behaviours/ValidationBehaviour.cs b/src/Application/Common/Behaviours/ValidationBehaviour.cs new file mode 100644 index 000000000..418a1621b --- /dev/null +++ b/src/Application/Common/Behaviours/ValidationBehaviour.cs @@ -0,0 +1,37 @@ +using FluentValidation; +using MediatR; +using ValidationException = Hippo.Application.Common.Exceptions.ValidationException; + +namespace Hippo.Application.Common.Behaviours; + +public class ValidationBehaviour : IPipelineBehavior + where TRequest : notnull +{ + private readonly IEnumerable> _validators; + + public ValidationBehaviour(IEnumerable> validators) + { + _validators = validators; + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + if (_validators.Any()) + { + var context = new ValidationContext(request); + + var validationResults = await Task.WhenAll( + _validators.Select(v => + v.ValidateAsync(context, cancellationToken))); + + var failures = validationResults + .Where(r => r.Errors.Any()) + .SelectMany(r => r.Errors) + .ToList(); + + if (failures.Any()) + throw new ValidationException(failures); + } + return await next(); + } +} diff --git a/src/Application/Common/Exceptions/CreateFailedException.cs b/src/Application/Common/Exceptions/CreateFailedException.cs new file mode 100644 index 000000000..209d04057 --- /dev/null +++ b/src/Application/Common/Exceptions/CreateFailedException.cs @@ -0,0 +1,24 @@ +namespace Hippo.Application.Common.Exceptions; + +public class CreateFailedException : Exception +{ + public CreateFailedException() + : base() + { + } + + public CreateFailedException(string message) + : base(message) + { + } + + public CreateFailedException(string message, Exception innerException) + : base(message, innerException) + { + } + + public CreateFailedException(string name, object key) + : base($"Created to fail \"{name}\" ({key}).") + { + } +} diff --git a/src/Application/Common/Exceptions/ForbiddenAccessException.cs b/src/Application/Common/Exceptions/ForbiddenAccessException.cs new file mode 100644 index 000000000..afd2b772b --- /dev/null +++ b/src/Application/Common/Exceptions/ForbiddenAccessException.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Common.Exceptions; + +public class ForbiddenAccessException : Exception +{ + public ForbiddenAccessException() : base() { } +} diff --git a/src/Application/Common/Exceptions/LoginFailedException.cs b/src/Application/Common/Exceptions/LoginFailedException.cs new file mode 100644 index 000000000..436053c1c --- /dev/null +++ b/src/Application/Common/Exceptions/LoginFailedException.cs @@ -0,0 +1,25 @@ +namespace Hippo.Application.Common.Exceptions; + +public class LoginFailedException : Exception +{ + public LoginFailedException() + : base($"Login failed.") + { + } + + public LoginFailedException(string message) + : base(message) + { + } + + // TODO: write unit tests using multiple reasons as input + public LoginFailedException(string[] reasons) + : base("Login failed: " + string.Join(", ", reasons)) + { + } + + public LoginFailedException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Application/Common/Exceptions/NotFoundException.cs b/src/Application/Common/Exceptions/NotFoundException.cs new file mode 100644 index 000000000..6c05f4927 --- /dev/null +++ b/src/Application/Common/Exceptions/NotFoundException.cs @@ -0,0 +1,24 @@ +namespace Hippo.Application.Common.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException() + : base() + { + } + + public NotFoundException(string message) + : base(message) + { + } + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + public NotFoundException(string name, object key) + : base($"Entity \"{name}\" ({key}) was not found.") + { + } +} diff --git a/src/Application/Common/Exceptions/ValidationException.cs b/src/Application/Common/Exceptions/ValidationException.cs new file mode 100644 index 000000000..a169b56d8 --- /dev/null +++ b/src/Application/Common/Exceptions/ValidationException.cs @@ -0,0 +1,22 @@ +using FluentValidation.Results; + +namespace Hippo.Application.Common.Exceptions; + +public class ValidationException : Exception +{ + public ValidationException() + : base("One or more validation failures have occurred.") + { + Errors = new Dictionary(); + } + + public ValidationException(IEnumerable failures) + : this() + { + Errors = failures + .GroupBy(e => e.PropertyName, e => e.ErrorMessage) + .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); + } + + public IDictionary Errors { get; } +} diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs new file mode 100644 index 000000000..7f7217c64 --- /dev/null +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -0,0 +1,19 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Common.Interfaces; + +public interface IApplicationDbContext +{ + DbSet Apps { get; } + + DbSet Channels { get; } + + DbSet Domains { get; } + + DbSet EnvironmentVariables { get; } + + DbSet Revisions { get; } + + Task SaveChangesAsync(CancellationToken cancellationToken); +} diff --git a/src/Application/Common/Interfaces/ICurrentUserService.cs b/src/Application/Common/Interfaces/ICurrentUserService.cs new file mode 100644 index 000000000..901484546 --- /dev/null +++ b/src/Application/Common/Interfaces/ICurrentUserService.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Common.Interfaces; + +public interface ICurrentUserService +{ + string? UserId { get; } +} diff --git a/src/Application/Common/Interfaces/IDateTime.cs b/src/Application/Common/Interfaces/IDateTime.cs new file mode 100644 index 000000000..23e2d16c1 --- /dev/null +++ b/src/Application/Common/Interfaces/IDateTime.cs @@ -0,0 +1,8 @@ +namespace Hippo.Application.Common.Interfaces; + +public interface IDateTime +{ + DateTime Now { get; } + + DateTime UtcNow { get; } +} diff --git a/src/Application/Common/Interfaces/IDomainEventService.cs b/src/Application/Common/Interfaces/IDomainEventService.cs new file mode 100644 index 000000000..8bf1d624b --- /dev/null +++ b/src/Application/Common/Interfaces/IDomainEventService.cs @@ -0,0 +1,8 @@ +using Hippo.Core.Common; + +namespace Hippo.Application.Common.Interfaces; + +public interface IDomainEventService +{ + Task Publish(DomainEvent domainEvent); +} diff --git a/src/Application/Common/Interfaces/IIdentityService.cs b/src/Application/Common/Interfaces/IIdentityService.cs new file mode 100644 index 000000000..889fd0fb3 --- /dev/null +++ b/src/Application/Common/Interfaces/IIdentityService.cs @@ -0,0 +1,20 @@ +using Hippo.Application.Common.Models; + +namespace Hippo.Application.Common.Interfaces; + +public interface IIdentityService +{ + Task GetUserNameAsync(string userId); + + Task GetUserIdAsync(string userName); + + Task IsInRoleAsync(string userId, string role); + + Task AuthorizeAsync(string userId, string policyName); + + Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password); + + Task DeleteUserAsync(string userId); + + Task CheckPasswordAsync(string userName, string password); +} diff --git a/src/Application/Common/Interfaces/IJobScheduler.cs b/src/Application/Common/Interfaces/IJobScheduler.cs new file mode 100644 index 000000000..c3b003632 --- /dev/null +++ b/src/Application/Common/Interfaces/IJobScheduler.cs @@ -0,0 +1,16 @@ +using Hippo.Core.Entities; + +namespace Hippo.Application.Common.Interfaces; + +public interface IJobScheduler +{ + /// + /// Schedule the current release. + /// + void Start(Channel c); + + /// + /// Gracefully shut down the current release. + /// + void Stop(Channel c); +} diff --git a/src/Application/Common/Interfaces/IJsonFileBuilder.cs b/src/Application/Common/Interfaces/IJsonFileBuilder.cs new file mode 100644 index 000000000..caa7b21c2 --- /dev/null +++ b/src/Application/Common/Interfaces/IJsonFileBuilder.cs @@ -0,0 +1,20 @@ +using Hippo.Application.Apps.Queries; +using Hippo.Application.Channels.Queries; +using Hippo.Application.Domains.Queries; +using Hippo.Application.EnvironmentVariables.Queries; +using Hippo.Application.Revisions.Queries; + +namespace Hippo.Application.Common.Interfaces; + +public interface IJsonFileBuilder +{ + byte[] BuildAppsFile(IEnumerable records); + + byte[] BuildChannelsFile(IEnumerable records); + + byte[] BuildDomainsFile(IEnumerable records); + + byte[] BuildEnvironmentVariablesFile(IEnumerable records); + + byte[] BuildRevisionsFile(IEnumerable records); +} diff --git a/src/Application/Common/Interfaces/IReverseProxy.cs b/src/Application/Common/Interfaces/IReverseProxy.cs new file mode 100644 index 000000000..0bcf4c069 --- /dev/null +++ b/src/Application/Common/Interfaces/IReverseProxy.cs @@ -0,0 +1,10 @@ +using Hippo.Core.Entities; + +namespace Hippo.Application.Common.Interfaces; + +public interface IReverseProxy +{ + void Start(Channel c, string address); + + void Stop(Channel c); +} diff --git a/src/Application/Common/Interfaces/ISignInService.cs b/src/Application/Common/Interfaces/ISignInService.cs new file mode 100644 index 000000000..c635662d9 --- /dev/null +++ b/src/Application/Common/Interfaces/ISignInService.cs @@ -0,0 +1,17 @@ +using Hippo.Application.Common.Models; +using MediatR; + +namespace Hippo.Application.Common.Interfaces; + +public interface ISignInService +{ + /// + /// Attempts to sign in the current user using the provided password. + /// + Task PasswordSignInAsync(string username, string password, bool rememberMe = false); + + /// + /// Signs the current user out of the application. + /// + Task SignOutAsync(); +} diff --git a/src/Application/Common/Interfaces/ITaskQueue.cs b/src/Application/Common/Interfaces/ITaskQueue.cs new file mode 100644 index 000000000..f9dfc132d --- /dev/null +++ b/src/Application/Common/Interfaces/ITaskQueue.cs @@ -0,0 +1,10 @@ +namespace Hippo.Application.Common.Interfaces; + +public interface ITaskQueue +{ + Task Enqueue(T value, CancellationToken cancellationToken); + + Task Dequeue(CancellationToken cancellationToken); + + (bool, T?) TryRead(); +} diff --git a/src/Application/Common/Interfaces/ITokenService.cs b/src/Application/Common/Interfaces/ITokenService.cs new file mode 100644 index 000000000..fe7bc95e2 --- /dev/null +++ b/src/Application/Common/Interfaces/ITokenService.cs @@ -0,0 +1,19 @@ +namespace Hippo.Application.Common.Interfaces; + +public interface ITokenService +{ + TokenInfo CreateSecurityToken(string userId); +} + +public class TokenInfo +{ + public string Token { get; } + + public DateTime Expiration { get; } + + public TokenInfo(string token, DateTime expiresIn) + { + Token = token; + Expiration = expiresIn; + } +} diff --git a/src/Application/Common/Mappings/IMapFrom.cs b/src/Application/Common/Mappings/IMapFrom.cs new file mode 100644 index 000000000..4dc13ceaf --- /dev/null +++ b/src/Application/Common/Mappings/IMapFrom.cs @@ -0,0 +1,8 @@ +using AutoMapper; + +namespace Hippo.Application.Common.Mappings; + +public interface IMapFrom +{ + void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType()); +} diff --git a/src/Application/Common/Mappings/MappingExtensions.cs b/src/Application/Common/Mappings/MappingExtensions.cs new file mode 100644 index 000000000..a14fd27d1 --- /dev/null +++ b/src/Application/Common/Mappings/MappingExtensions.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Models; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Common.Mappings; + +public static class MappingExtensions +{ + public static Task> PaginatedListAsync(this IQueryable queryable, int pageNumber, int pageSize) + => PaginatedList.CreateAsync(queryable, pageNumber, pageSize); + + public static Task> ProjectToListAsync(this IQueryable queryable, IConfigurationProvider configuration) + => queryable.ProjectTo(configuration).ToListAsync(); +} diff --git a/src/Application/Common/Mappings/MappingProfile.cs b/src/Application/Common/Mappings/MappingProfile.cs new file mode 100644 index 000000000..3883e97d8 --- /dev/null +++ b/src/Application/Common/Mappings/MappingProfile.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using AutoMapper; + +namespace Hippo.Application.Common.Mappings; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly()); + } + + private void ApplyMappingsFromAssembly(Assembly assembly) + { + var types = assembly.GetExportedTypes() + .Where(t => t.GetInterfaces().Any(i => + i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>))) + .ToList(); + + foreach (var type in types) + { + var instance = Activator.CreateInstance(type); + + var methodInfo = type.GetMethod("Mapping") + ?? type.GetInterface("IMapFrom`1")!.GetMethod("Mapping"); + + methodInfo?.Invoke(instance, new object[] { this }); + + } + } +} diff --git a/src/Application/Common/Models/DomainEventNotification.cs b/src/Application/Common/Models/DomainEventNotification.cs new file mode 100644 index 000000000..c5fc8b5c4 --- /dev/null +++ b/src/Application/Common/Models/DomainEventNotification.cs @@ -0,0 +1,14 @@ +using Hippo.Core.Common; +using MediatR; + +namespace Hippo.Application.Common.Models; + +public class DomainEventNotification : INotification where TDomainEvent : DomainEvent +{ + public DomainEventNotification(TDomainEvent domainEvent) + { + DomainEvent = domainEvent; + } + + public TDomainEvent DomainEvent { get; } +} diff --git a/src/Application/Common/Models/PaginatedList.cs b/src/Application/Common/Models/PaginatedList.cs new file mode 100644 index 000000000..70e1d5d1f --- /dev/null +++ b/src/Application/Common/Models/PaginatedList.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Common.Models; + +public class PaginatedList +{ + public List Items { get; } + public int PageNumber { get; } + public int TotalPages { get; } + public int TotalCount { get; } + + public PaginatedList(List items, int count, int pageNumber, int pageSize) + { + PageNumber = pageNumber; + TotalPages = (int)Math.Ceiling(count / (double)pageSize); + TotalCount = count; + Items = items; + } + + public bool HasPreviousPage => PageNumber > 1; + + public bool HasNextPage => PageNumber < TotalPages; + + public static async Task> CreateAsync(IQueryable source, int pageNumber, int pageSize) + { + var count = await source.CountAsync(); + var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync(); + + return new PaginatedList(items, count, pageNumber, pageSize); + } +} diff --git a/src/Application/Common/Models/Result.cs b/src/Application/Common/Models/Result.cs new file mode 100644 index 000000000..d928f233d --- /dev/null +++ b/src/Application/Common/Models/Result.cs @@ -0,0 +1,24 @@ +namespace Hippo.Application.Common.Models; + +public class Result +{ + internal Result(bool succeeded, IEnumerable errors) + { + Succeeded = succeeded; + Errors = errors.ToArray(); + } + + public bool Succeeded { get; set; } + + public string[] Errors { get; set; } + + public static Result Success() + { + return new Result(true, Array.Empty()); + } + + public static Result Failure(IEnumerable errors) + { + return new Result(false, errors); + } +} diff --git a/src/Application/Common/Security/AuthorizeAttribute.cs b/src/Application/Common/Security/AuthorizeAttribute.cs new file mode 100644 index 000000000..4b73ccef1 --- /dev/null +++ b/src/Application/Common/Security/AuthorizeAttribute.cs @@ -0,0 +1,23 @@ +namespace Hippo.Application.Common.Security; + +/// +/// Specifies the class this attribute is applied to requires authorization. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class AuthorizeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public AuthorizeAttribute() { } + + /// + /// Gets or sets a comma delimited list of roles that are allowed to access the resource. + /// + public string Roles { get; set; } = string.Empty; + + /// + /// Gets or sets the policy name that determines access to the resource. + /// + public string Policy { get; set; } = string.Empty; +} diff --git a/src/Application/DependencyInjection.cs b/src/Application/DependencyInjection.cs new file mode 100644 index 000000000..8465ae2d6 --- /dev/null +++ b/src/Application/DependencyInjection.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using Hippo.Application.Common.Behaviours; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.DependencyInjection; + +namespace Hippo.Application; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddAutoMapper(Assembly.GetExecutingAssembly()); + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + services.AddMediatR(Assembly.GetExecutingAssembly()); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehaviour<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); + + return services; + } +} diff --git a/src/Application/Domains/Commands/CreateDomainCommand.cs b/src/Application/Domains/Commands/CreateDomainCommand.cs new file mode 100644 index 000000000..e55446041 --- /dev/null +++ b/src/Application/Domains/Commands/CreateDomainCommand.cs @@ -0,0 +1,37 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Domains.Commands; + +public class CreateDomainCommand : IRequest +{ + public string? Name { get; set; } +} + +public class CreateDomainCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateDomainCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateDomainCommand request, CancellationToken cancellationToken) + { + var entity = new Domain + { + Name = request.Name + }; + + entity.DomainEvents.Add(new DomainCreatedEvent(entity)); + + _context.Domains.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/Application/Domains/Commands/CreateDomainCommandValidator.cs b/src/Application/Domains/Commands/CreateDomainCommandValidator.cs new file mode 100644 index 000000000..ec5ae81eb --- /dev/null +++ b/src/Application/Domains/Commands/CreateDomainCommandValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; + +namespace Hippo.Application.Domains.Commands; + +public class CreateDomainCommandValidator : AbstractValidator +{ + public CreateDomainCommandValidator() + { + RuleFor(v => v.Name) + .MaximumLength(200) + .NotEmpty(); + } +} diff --git a/src/Application/Domains/Commands/DeleteDomainCommand.cs b/src/Application/Domains/Commands/DeleteDomainCommand.cs new file mode 100644 index 000000000..da29d1aa9 --- /dev/null +++ b/src/Application/Domains/Commands/DeleteDomainCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Domains.Commands; + +public class DeleteDomainCommand : IRequest +{ + public Guid Id { get; set; } +} + +public class DeleteDomainCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteDomainCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteDomainCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Domains + .Where(l => l.Id == request.Id) + .SingleOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(Domain), request.Id); + } + + _context.Domains.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Domains/Commands/PurgeDomainsCommand.cs b/src/Application/Domains/Commands/PurgeDomainsCommand.cs new file mode 100644 index 000000000..d65d33d3e --- /dev/null +++ b/src/Application/Domains/Commands/PurgeDomainsCommand.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.Domains.Commands; + +[Authorize(Roles = "Administrator")] +[Authorize(Policy = "CanPurge")] +public class PurgeDomainsCommand : IRequest +{ +} + +public class PurgeDomainsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public PurgeDomainsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(PurgeDomainsCommand request, CancellationToken cancellationToken) + { + _context.Domains.RemoveRange(_context.Domains); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Domains/EventHandlers/DomainCreatedEventHandler.cs b/src/Application/Domains/EventHandlers/DomainCreatedEventHandler.cs new file mode 100644 index 000000000..0c83b7f8b --- /dev/null +++ b/src/Application/Domains/EventHandlers/DomainCreatedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Domains.EventHandlers; + +public class DomainCreatedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public DomainCreatedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Domains/EventHandlers/DomainDeletedEventHandler.cs b/src/Application/Domains/EventHandlers/DomainDeletedEventHandler.cs new file mode 100644 index 000000000..728fe3335 --- /dev/null +++ b/src/Application/Domains/EventHandlers/DomainDeletedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Domains.EventHandlers; + +public class DomainDeletedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public DomainDeletedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/Domains/Queries/DomainDto.cs b/src/Application/Domains/Queries/DomainDto.cs new file mode 100644 index 000000000..5f864fa31 --- /dev/null +++ b/src/Application/Domains/Queries/DomainDto.cs @@ -0,0 +1,13 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.Domains.Queries; + +public class DomainDto : IMapFrom +{ + public Guid Id { get; set; } + + public Guid ChannelId { get; set; } + + public string? Name { get; set; } +} diff --git a/src/Application/Domains/Queries/DomainRecord.cs b/src/Application/Domains/Queries/DomainRecord.cs new file mode 100644 index 000000000..7d8da447c --- /dev/null +++ b/src/Application/Domains/Queries/DomainRecord.cs @@ -0,0 +1,9 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.Domains.Queries; + +public class DomainRecord : IMapFrom +{ + public string? Name { get; set; } +} diff --git a/src/Application/Domains/Queries/DomainsVm.cs b/src/Application/Domains/Queries/DomainsVm.cs new file mode 100644 index 000000000..bad8cd412 --- /dev/null +++ b/src/Application/Domains/Queries/DomainsVm.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Domains.Queries; + +public class DomainsVm +{ + public IList Domains { get; set; } = new List(); +} diff --git a/src/Application/Domains/Queries/ExportDomainsQuery.cs b/src/Application/Domains/Queries/ExportDomainsQuery.cs new file mode 100644 index 000000000..7f1fc2666 --- /dev/null +++ b/src/Application/Domains/Queries/ExportDomainsQuery.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Domains.Queries; + +public class ExportDomainsQuery : IRequest +{ +} + +public class ExportDomainsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IJsonFileBuilder _fileBuilder; + + public ExportDomainsQueryHandler(IApplicationDbContext context, IMapper mapper, IJsonFileBuilder fileBuilder) + { + _context = context; + _mapper = mapper; + _fileBuilder = fileBuilder; + } + + public async Task Handle(ExportDomainsQuery request, CancellationToken cancellationToken) + { + var records = await _context.Domains + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + var vm = new ExportDomainsVm( + "domains.json", + "application/json", + _fileBuilder.BuildDomainsFile(records)); + return vm; + } +} diff --git a/src/Application/Domains/Queries/ExportDomainsVm.cs b/src/Application/Domains/Queries/ExportDomainsVm.cs new file mode 100644 index 000000000..83feb9617 --- /dev/null +++ b/src/Application/Domains/Queries/ExportDomainsVm.cs @@ -0,0 +1,17 @@ +namespace Hippo.Application.Domains.Queries; + +public class ExportDomainsVm +{ + public ExportDomainsVm(string fileName, string contentType, byte[] content) + { + FileName = fileName; + ContentType = contentType; + Content = content; + } + + public string FileName { get; set; } + + public string ContentType { get; set; } + + public byte[] Content { get; set; } +} diff --git a/src/Application/Domains/Queries/GetDomainsQuery.cs b/src/Application/Domains/Queries/GetDomainsQuery.cs new file mode 100644 index 000000000..bc316f47d --- /dev/null +++ b/src/Application/Domains/Queries/GetDomainsQuery.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Domains.Queries; + +public class GetDomainsQuery : IRequest +{ +} + +public class GetDomainsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetDomainsQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetDomainsQuery request, CancellationToken cancellationToken) + { + return new DomainsVm + { + Domains = await _context.Domains + .ProjectTo(_mapper.ConfigurationProvider) + .OrderBy(d => d.Name) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommand.cs b/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommand.cs new file mode 100644 index 000000000..a98e1f0a8 --- /dev/null +++ b/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.EnvironmentVariables.Commands; + +public class CreateEnvironmentVariableCommand : IRequest +{ + public string? Key { get; set; } + + public string? Value { get; set; } +} + +public class CreateEnvironmentVariableCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateEnvironmentVariableCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateEnvironmentVariableCommand request, CancellationToken cancellationToken) + { + var entity = new EnvironmentVariable + { + Key = request.Key, + Value = request.Value + }; + + entity.DomainEvents.Add(new EnvironmentVariableCreatedEvent(entity)); + + _context.EnvironmentVariables.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommandValidator.cs b/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommandValidator.cs new file mode 100644 index 000000000..2d50c8400 --- /dev/null +++ b/src/Application/EnvironmentVariables/Commands/CreateEnvironmentVariableCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; + +namespace Hippo.Application.EnvironmentVariables.Commands; + +public class CreateEnvironmentVariableCommandValidator : AbstractValidator +{ + public CreateEnvironmentVariableCommandValidator() + { + RuleFor(v => v.Key) + .MaximumLength(32) + .NotEmpty(); + + RuleFor(v => v.Value) + .MaximumLength(200) + .NotEmpty(); + } +} diff --git a/src/Application/EnvironmentVariables/Commands/DeleteEnvironmentVariableCommand.cs b/src/Application/EnvironmentVariables/Commands/DeleteEnvironmentVariableCommand.cs new file mode 100644 index 000000000..a1842c19c --- /dev/null +++ b/src/Application/EnvironmentVariables/Commands/DeleteEnvironmentVariableCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.EnvironmentVariables.Commands; + +public class DeleteEnvironmentVariableCommand : IRequest +{ + public Guid Id { get; set; } +} + +public class DeleteEnvironmentVariableCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteEnvironmentVariableCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteEnvironmentVariableCommand request, CancellationToken cancellationToken) + { + var entity = await _context.EnvironmentVariables + .Where(l => l.Id == request.Id) + .SingleOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(EnvironmentVariable), request.Id); + } + + _context.EnvironmentVariables.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/EnvironmentVariables/Commands/PurgeEnvironmentVariablesCommand.cs b/src/Application/EnvironmentVariables/Commands/PurgeEnvironmentVariablesCommand.cs new file mode 100644 index 000000000..5a9a5d176 --- /dev/null +++ b/src/Application/EnvironmentVariables/Commands/PurgeEnvironmentVariablesCommand.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.EnvironmentVariables.Commands; + +[Authorize(Roles = "Administrator")] +[Authorize(Policy = "CanPurge")] +public class PurgeEnvironmentVariablesCommand : IRequest +{ +} + +public class PurgeEnvironmentVariablesCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public PurgeEnvironmentVariablesCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(PurgeEnvironmentVariablesCommand request, CancellationToken cancellationToken) + { + _context.EnvironmentVariables.RemoveRange(_context.EnvironmentVariables); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableCreatedEventHandler.cs b/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableCreatedEventHandler.cs new file mode 100644 index 000000000..7a6ad443f --- /dev/null +++ b/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableCreatedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.EnvironmentVariables.EventHandlers; + +public class EnvironmentVariableCreatedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public EnvironmentVariableCreatedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableDeletedEventHandler.cs b/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableDeletedEventHandler.cs new file mode 100644 index 000000000..a1e2d8fa4 --- /dev/null +++ b/src/Application/EnvironmentVariables/EventHandlers/EnvironmentVariableDeletedEventHandler.cs @@ -0,0 +1,25 @@ +using Hippo.Application.Common.Models; +using Hippo.Core.Events; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.EnvironmentVariables.EventHandlers; + +public class EnvironmentVariableDeletedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + public EnvironmentVariableDeletedEventHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + return Task.CompletedTask; + } +} diff --git a/src/Application/EnvironmentVariables/Queries/EnvironmentVariableDto.cs b/src/Application/EnvironmentVariables/Queries/EnvironmentVariableDto.cs new file mode 100644 index 000000000..02386d039 --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/EnvironmentVariableDto.cs @@ -0,0 +1,15 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class EnvironmentVariableDto : IMapFrom +{ + public Guid Id { get; set; } + + public Guid ChannelId { get; set; } + + public string? Key { get; set; } + + public string? Value { get; set; } +} diff --git a/src/Application/EnvironmentVariables/Queries/EnvironmentVariableRecord.cs b/src/Application/EnvironmentVariables/Queries/EnvironmentVariableRecord.cs new file mode 100644 index 000000000..bd2a76293 --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/EnvironmentVariableRecord.cs @@ -0,0 +1,11 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class EnvironmentVariableRecord : IMapFrom +{ + public string? Key { get; set; } + + public string? Value { get; set; } +} diff --git a/src/Application/EnvironmentVariables/Queries/EnvironmentVariablesVm.cs b/src/Application/EnvironmentVariables/Queries/EnvironmentVariablesVm.cs new file mode 100644 index 000000000..fc482463c --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/EnvironmentVariablesVm.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class EnvironmentVariablesVm +{ + public IList EnvironmentVariables { get; set; } = new List(); +} diff --git a/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesQuery.cs b/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesQuery.cs new file mode 100644 index 000000000..abae788d1 --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesQuery.cs @@ -0,0 +1,39 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class ExportEnvironmentVariablesQuery : IRequest +{ +} + +public class ExportEnvironmentVariablesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IJsonFileBuilder _fileBuilder; + + public ExportEnvironmentVariablesQueryHandler(IApplicationDbContext context, IMapper mapper, IJsonFileBuilder fileBuilder) + { + _context = context; + _mapper = mapper; + _fileBuilder = fileBuilder; + } + + public async Task Handle(ExportEnvironmentVariablesQuery request, CancellationToken cancellationToken) + { + var records = await _context.EnvironmentVariables + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + var vm = new ExportEnvironmentVariablesVm( + "environment-variables.json", + "application/json", + _fileBuilder.BuildEnvironmentVariablesFile(records)); + + return vm; + } +} diff --git a/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesVm.cs b/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesVm.cs new file mode 100644 index 000000000..20d39f2f4 --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/ExportEnvironmentVariablesVm.cs @@ -0,0 +1,17 @@ +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class ExportEnvironmentVariablesVm +{ + public ExportEnvironmentVariablesVm(string fileName, string contentType, byte[] content) + { + FileName = fileName; + ContentType = contentType; + Content = content; + } + + public string FileName { get; set; } + + public string ContentType { get; set; } + + public byte[] Content { get; set; } +} diff --git a/src/Application/EnvironmentVariables/Queries/GetEnvironmentVariablesQuery.cs b/src/Application/EnvironmentVariables/Queries/GetEnvironmentVariablesQuery.cs new file mode 100644 index 000000000..35c7c2332 --- /dev/null +++ b/src/Application/EnvironmentVariables/Queries/GetEnvironmentVariablesQuery.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.EnvironmentVariables.Queries; + +public class GetEnvironmentVariablesQuery : IRequest +{ +} + +public class GetEnvironmentVariablesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetEnvironmentVariablesQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetEnvironmentVariablesQuery request, CancellationToken cancellationToken) + { + return new EnvironmentVariablesVm + { + EnvironmentVariables = await _context.EnvironmentVariables + .ProjectTo(_mapper.ConfigurationProvider) + .OrderBy(e => e.Key) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/Application/Revisions/Commands/CreateRevisionCommand.cs b/src/Application/Revisions/Commands/CreateRevisionCommand.cs new file mode 100644 index 000000000..96551e3e5 --- /dev/null +++ b/src/Application/Revisions/Commands/CreateRevisionCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Core.Events; +using MediatR; + +namespace Hippo.Application.Revisions.Commands; + +public class CreateRevisionCommand : IRequest +{ + public Guid AppId { get; set; } + + public string? RevisionNumber { get; set; } +} + +public class CreateRevisionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateRevisionCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateRevisionCommand request, CancellationToken cancellationToken) + { + var entity = new Revision + { + AppId = request.AppId, + RevisionNumber = request.RevisionNumber + }; + + entity.DomainEvents.Add(new RevisionCreatedEvent(entity)); + + _context.Revisions.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/Application/Revisions/Commands/CreateRevisionCommandValidator.cs b/src/Application/Revisions/Commands/CreateRevisionCommandValidator.cs new file mode 100644 index 000000000..c0d4709f6 --- /dev/null +++ b/src/Application/Revisions/Commands/CreateRevisionCommandValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; + +namespace Hippo.Application.Revisions.Commands; + +public class CreateRevisionCommandValidator : AbstractValidator +{ + public CreateRevisionCommandValidator() + { + RuleFor(v => v.RevisionNumber) + .MaximumLength(32) + .NotEmpty(); + } +} diff --git a/src/Application/Revisions/Commands/DeleteRevisionCommand.cs b/src/Application/Revisions/Commands/DeleteRevisionCommand.cs new file mode 100644 index 000000000..14895028d --- /dev/null +++ b/src/Application/Revisions/Commands/DeleteRevisionCommand.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Revisions.Commands; + +public class DeleteRevisionCommand : IRequest +{ + public Guid Id { get; set; } +} + +public class DeleteRevisionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteRevisionCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteRevisionCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Revisions + .Where(l => l.Id == request.Id) + .SingleOrDefaultAsync(cancellationToken); + + if (entity == null) + { + throw new NotFoundException(nameof(Revision), request.Id); + } + + _context.Revisions.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Revisions/Commands/PurgeRevisionsCommand.cs b/src/Application/Revisions/Commands/PurgeRevisionsCommand.cs new file mode 100644 index 000000000..1acd19bc5 --- /dev/null +++ b/src/Application/Revisions/Commands/PurgeRevisionsCommand.cs @@ -0,0 +1,30 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Security; +using MediatR; + +namespace Hippo.Application.Revisions.Commands; + +[Authorize(Roles = "Administrator")] +[Authorize(Policy = "CanPurge")] +public class PurgeRevisionsCommand : IRequest +{ +} + +public class PurgeRevisionsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public PurgeRevisionsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(PurgeRevisionsCommand request, CancellationToken cancellationToken) + { + _context.Revisions.RemoveRange(_context.Revisions); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/Application/Revisions/EventHandlers/RevisionCreatedEventHandler.cs b/src/Application/Revisions/EventHandlers/RevisionCreatedEventHandler.cs new file mode 100644 index 000000000..b16e2dd57 --- /dev/null +++ b/src/Application/Revisions/EventHandlers/RevisionCreatedEventHandler.cs @@ -0,0 +1,51 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using Hippo.Application.Rules; +using Hippo.Core.Entities; +using Hippo.Core.Enums; +using Hippo.Core.Events; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Revisions.EventHandlers; + +public class RevisionCreatedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + private readonly IApplicationDbContext _context; + + public RevisionCreatedEventHandler(ILogger logger, IApplicationDbContext context) + { + _logger = logger; + _context = context; + } + + public async Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + // re-evaluate active revisions for every channel related to the same app + var channels = await _context.Channels + .Where(c => c.AppId == domainEvent.Revision.AppId) + .ToListAsync(cancellationToken); + + foreach (Channel channel in channels) + { + if (channel.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision) + { + var newActiveRevision = RevisionRangeRule.Parse(channel.RangeRule).Match(channel.App.Revisions); + if (newActiveRevision != null && newActiveRevision != channel.ActiveRevision) + { + channel.DomainEvents.Add(new ActiveRevisionChangedEvent(channel, newActiveRevision)); + } + channel.ActiveRevision = newActiveRevision; + } + } + + await _context.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/Application/Revisions/EventHandlers/RevisionDeletedEventHandler.cs b/src/Application/Revisions/EventHandlers/RevisionDeletedEventHandler.cs new file mode 100644 index 000000000..3762e07e0 --- /dev/null +++ b/src/Application/Revisions/EventHandlers/RevisionDeletedEventHandler.cs @@ -0,0 +1,51 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using Hippo.Application.Rules; +using Hippo.Core.Entities; +using Hippo.Core.Enums; +using Hippo.Core.Events; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Hippo.Application.Revisions.EventHandlers; + +public class RevisionDeletedEventHandler : INotificationHandler> +{ + private readonly ILogger _logger; + + private readonly IApplicationDbContext _context; + + public RevisionDeletedEventHandler(ILogger logger, IApplicationDbContext context) + { + _logger = logger; + _context = context; + } + + public async Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var domainEvent = notification.DomainEvent; + + _logger.LogInformation("Hippo Domain Event: {DomainEvent}", domainEvent.GetType().Name); + + // re-evaluate active revisions for every channel related to the same app + var channels = await _context.Channels + .Where(c => c.AppId == domainEvent.Revision.AppId) + .ToListAsync(cancellationToken); + + foreach (Channel channel in channels) + { + if (channel.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision) + { + var newActiveRevision = RevisionRangeRule.Parse(channel.RangeRule).Match(channel.App.Revisions); + if (newActiveRevision != null && newActiveRevision != channel.ActiveRevision) + { + channel.DomainEvents.Add(new ActiveRevisionChangedEvent(channel, newActiveRevision)); + } + channel.ActiveRevision = newActiveRevision; + } + } + + await _context.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/Application/Revisions/Queries/ExportRevisionsQuery.cs b/src/Application/Revisions/Queries/ExportRevisionsQuery.cs new file mode 100644 index 000000000..73affbb51 --- /dev/null +++ b/src/Application/Revisions/Queries/ExportRevisionsQuery.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Revisions.Queries; + +public class ExportRevisionsQuery : IRequest +{ +} + +public class ExportRevisionsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + private readonly IJsonFileBuilder _fileBuilder; + + public ExportRevisionsQueryHandler(IApplicationDbContext context, IMapper mapper, IJsonFileBuilder fileBuilder) + { + _context = context; + _mapper = mapper; + _fileBuilder = fileBuilder; + } + + public async Task Handle(ExportRevisionsQuery request, CancellationToken cancellationToken) + { + var records = await _context.Revisions + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + var vm = new ExportRevisionsVm( + "revisions.json", + "application/json", + _fileBuilder.BuildRevisionsFile(records)); + return vm; + } +} diff --git a/src/Application/Revisions/Queries/ExportRevisionsVm.cs b/src/Application/Revisions/Queries/ExportRevisionsVm.cs new file mode 100644 index 000000000..a4cf9033f --- /dev/null +++ b/src/Application/Revisions/Queries/ExportRevisionsVm.cs @@ -0,0 +1,17 @@ +namespace Hippo.Application.Revisions.Queries; + +public class ExportRevisionsVm +{ + public ExportRevisionsVm(string fileName, string contentType, byte[] content) + { + FileName = fileName; + ContentType = contentType; + Content = content; + } + + public string FileName { get; set; } + + public string ContentType { get; set; } + + public byte[] Content { get; set; } +} diff --git a/src/Application/Revisions/Queries/GetRevisionsQuery.cs b/src/Application/Revisions/Queries/GetRevisionsQuery.cs new file mode 100644 index 000000000..2a8cf820c --- /dev/null +++ b/src/Application/Revisions/Queries/GetRevisionsQuery.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Hippo.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Application.Revisions.Queries; + +public class GetRevisionsQuery : IRequest +{ +} + +public class GetRevisionsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + private readonly IMapper _mapper; + + public GetRevisionsQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetRevisionsQuery request, CancellationToken cancellationToken) + { + return new RevisionsVm + { + Revisions = await _context.Revisions + .ProjectTo(_mapper.ConfigurationProvider) + .OrderBy(r => r.RevisionNumber) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/Application/Revisions/Queries/RevisionDto.cs b/src/Application/Revisions/Queries/RevisionDto.cs new file mode 100644 index 000000000..9cb18f9cf --- /dev/null +++ b/src/Application/Revisions/Queries/RevisionDto.cs @@ -0,0 +1,22 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.Revisions.Queries; + +public class RevisionDto : IMapFrom +{ + public Guid Id { get; set; } + + public Guid AppId { get; set; } + + public string? RevisionNumber { get; set; } + + public string OrderKey() + { + if (SemVer.Version.TryParse(RevisionNumber, out var version)) + { + return $"{version.Major:D9}{version.Minor:D9}{version.Patch:D9}{RevisionNumber}"; + } + return RevisionNumber!; + } +} diff --git a/src/Application/Revisions/Queries/RevisionRecord.cs b/src/Application/Revisions/Queries/RevisionRecord.cs new file mode 100644 index 000000000..6eab84cf0 --- /dev/null +++ b/src/Application/Revisions/Queries/RevisionRecord.cs @@ -0,0 +1,9 @@ +using Hippo.Application.Common.Mappings; +using Hippo.Core.Entities; + +namespace Hippo.Application.Revisions.Queries; + +public class RevisionRecord : IMapFrom +{ + public string? RevisionNumber { get; set; } +} diff --git a/src/Application/Revisions/Queries/RevisionsVm.cs b/src/Application/Revisions/Queries/RevisionsVm.cs new file mode 100644 index 000000000..0aa9698e8 --- /dev/null +++ b/src/Application/Revisions/Queries/RevisionsVm.cs @@ -0,0 +1,6 @@ +namespace Hippo.Application.Revisions.Queries; + +public class RevisionsVm +{ + public IList Revisions { get; set; } = new List(); +} diff --git a/src/Hippo/Rules/RevisionRangeRule.cs b/src/Application/Rules/RevisionRangeRule.cs similarity index 94% rename from src/Hippo/Rules/RevisionRangeRule.cs rename to src/Application/Rules/RevisionRangeRule.cs index c5e12d10f..74ff4db93 100644 --- a/src/Hippo/Rules/RevisionRangeRule.cs +++ b/src/Application/Rules/RevisionRangeRule.cs @@ -1,12 +1,7 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Linq; -using Hippo.Models; +using Hippo.Core.Entities; using Range = SemVer.Range; -namespace Hippo.Rules; +namespace Hippo.Application.Rules; public abstract class RevisionRangeRule { @@ -50,7 +45,7 @@ public NpmRevisionRangeRule(string rule) public override Revision? Match(ICollection candidates) { - var candidatesByVersion = candidates.ToDictionary(c => c.RevisionNumber, c => c); + var candidatesByVersion = candidates.ToDictionary(c => c.RevisionNumber!, c => c); var maxSatisfying = _range.MaxSatisfying(candidatesByVersion.Keys); if (maxSatisfying == null) @@ -95,7 +90,7 @@ public PrereleaseRevisionRangeRule(string rule) { var candidatesByVersion = from c in candidates - from pr in ParsePrerelease(c.RevisionNumber) + from pr in ParsePrerelease(c.RevisionNumber!) where Matches(pr) orderby pr.Timestamp descending select c; diff --git a/src/Core/Common/AuditableEntity.cs b/src/Core/Common/AuditableEntity.cs new file mode 100644 index 000000000..3153e3444 --- /dev/null +++ b/src/Core/Common/AuditableEntity.cs @@ -0,0 +1,20 @@ +namespace Hippo.Core.Common; + +public abstract class AuditableEntity +{ + + protected AuditableEntity() + { + Created = DateTime.UtcNow; + LastModified = DateTime.UtcNow; + } + + public DateTime Created { get; set; } + + public string? CreatedBy { get; set; } + + public DateTime LastModified { get; set; } + + public string? LastModifiedBy { get; set; } + +} diff --git a/src/Core/Common/DomainEvent.cs b/src/Core/Common/DomainEvent.cs new file mode 100644 index 000000000..9ed9f98e9 --- /dev/null +++ b/src/Core/Common/DomainEvent.cs @@ -0,0 +1,16 @@ +namespace Hippo.Core.Common; + +public interface IHasDomainEvent +{ + public List DomainEvents { get; set; } +} + +public abstract class DomainEvent +{ + protected DomainEvent() + { + DateOccurred = DateTimeOffset.UtcNow; + } + public bool IsPublished { get; set; } + public DateTimeOffset DateOccurred { get; protected set; } = DateTime.UtcNow; +} diff --git a/src/Core/Common/ValueObject.cs b/src/Core/Common/ValueObject.cs new file mode 100644 index 000000000..1b4170423 --- /dev/null +++ b/src/Core/Common/ValueObject.cs @@ -0,0 +1,40 @@ +namespace Hippo.Core.Common; + +// Learn more: https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects +public abstract class ValueObject +{ + protected static bool EqualOperator(ValueObject left, ValueObject right) + { + if (left is null ^ right is null) + { + return false; + } + + return left?.Equals(right!) != false; + } + + protected static bool NotEqualOperator(ValueObject left, ValueObject right) + { + return !(EqualOperator(left, right)); + } + + protected abstract IEnumerable GetEqualityComponents(); + + public override bool Equals(object? obj) + { + if (obj == null || obj.GetType() != GetType()) + { + return false; + } + + var other = (ValueObject)obj; + return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + + public override int GetHashCode() + { + return GetEqualityComponents() + .Select(x => x != null ? x.GetHashCode() : 0) + .Aggregate((x, y) => x ^ y); + } +} diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj new file mode 100644 index 000000000..a43434586 --- /dev/null +++ b/src/Core/Core.csproj @@ -0,0 +1,11 @@ + + + + net6.0 + Hippo.Core + Hippo.Core + enable + enable + + + diff --git a/src/Core/Entities/App.cs b/src/Core/Entities/App.cs new file mode 100644 index 000000000..0c795cf7a --- /dev/null +++ b/src/Core/Entities/App.cs @@ -0,0 +1,22 @@ +namespace Hippo.Core.Entities; + +public class App : AuditableEntity, IHasDomainEvent +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + // This is the ID in Bindle or whatever storage backend is used. It gets composed + // with a revision ID to get a Bindle ID. + // + // For example, the Weather application might have the StorageId contoso/weather. + // Revision 1.4.0 of the Weather application would then have the bindle id + // contoso/weather/1.4.0 + public string? StorageId { get; set; } + + public IList Channels { get; private set; } = new List(); + + public IList Revisions { get; private set; } = new List(); + + public List DomainEvents { get; set; } = new List(); +} diff --git a/src/Core/Entities/Channel.cs b/src/Core/Entities/Channel.cs new file mode 100644 index 000000000..ab435bfec --- /dev/null +++ b/src/Core/Entities/Channel.cs @@ -0,0 +1,28 @@ +using Hippo.Core.Enums; + +namespace Hippo.Core.Entities; + +public class Channel : AuditableEntity, IHasDomainEvent +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } + + public string? RangeRule { get; set; } + + public Revision? ActiveRevision { get; set; } + + public int PortId { get; set; } + + public Guid AppId { get; set; } + + public App App { get; set; } = null!; + + public Domain? Domain { get; private set; } + + public IList EnvironmentVariables { get; private set; } = new List(); + + public List DomainEvents { get; set; } = new List(); +} diff --git a/src/Core/Entities/Domain.cs b/src/Core/Entities/Domain.cs new file mode 100644 index 000000000..f3630532e --- /dev/null +++ b/src/Core/Entities/Domain.cs @@ -0,0 +1,14 @@ +namespace Hippo.Core.Entities; + +public class Domain : AuditableEntity, IHasDomainEvent +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public Guid ChannelId { get; set; } + + public Channel Channel { get; set; } = null!; + + public List DomainEvents { get; set; } = new List(); +} diff --git a/src/Core/Entities/EnvironmentVariable.cs b/src/Core/Entities/EnvironmentVariable.cs new file mode 100644 index 000000000..6a1dde104 --- /dev/null +++ b/src/Core/Entities/EnvironmentVariable.cs @@ -0,0 +1,16 @@ +namespace Hippo.Core.Entities; + +public class EnvironmentVariable : AuditableEntity, IHasDomainEvent +{ + public Guid Id { get; set; } + + public string? Key { get; set; } + + public string? Value { get; set; } + + public Guid ChannelId { get; set; } + + public Channel Channel { get; set; } = null!; + + public List DomainEvents { get; set; } = new List(); +} diff --git a/src/Core/Entities/Revision.cs b/src/Core/Entities/Revision.cs new file mode 100644 index 000000000..be7dfa890 --- /dev/null +++ b/src/Core/Entities/Revision.cs @@ -0,0 +1,16 @@ +namespace Hippo.Core.Entities; + +public class Revision : AuditableEntity, IHasDomainEvent +{ + public Guid Id { get; set; } + + // This is the revision number that gets composed with the Application.StorageId + // to get the bindle ID. E.g. this might be "1.4.0" or "1.1.5-prerelease2". + public string? RevisionNumber { get; set; } + + public Guid AppId { get; set; } + + public App App { get; set; } = null!; + + public List DomainEvents { get; set; } = new List(); +} diff --git a/src/Core/Enums/ChannelRevisionSelectionStrategy.cs b/src/Core/Enums/ChannelRevisionSelectionStrategy.cs new file mode 100644 index 000000000..b9bce3c56 --- /dev/null +++ b/src/Core/Enums/ChannelRevisionSelectionStrategy.cs @@ -0,0 +1,16 @@ +namespace Hippo.Core.Enums; + +/// +/// The strategy to use to select a revision for the Channel +/// +public enum ChannelRevisionSelectionStrategy +{ + /// + /// Use a range rule to select the most appropriate revision for the channel + /// + UseRangeRule = 0, + /// + /// Use a specific revision version for the channel + /// + UseSpecifiedRevision = 1, +} diff --git a/src/Core/Events/ActiveRevisionChangedEvent.cs b/src/Core/Events/ActiveRevisionChangedEvent.cs new file mode 100644 index 000000000..e2b47fb19 --- /dev/null +++ b/src/Core/Events/ActiveRevisionChangedEvent.cs @@ -0,0 +1,17 @@ +namespace Hippo.Core.Events; + +public class ActiveRevisionChangedEvent : DomainEvent +{ + public ActiveRevisionChangedEvent(Channel channel, Revision changedTo) + { + Channel = channel; + ChangedFrom = channel.ActiveRevision; + ChangedTo = changedTo; + } + + public Channel Channel { get; } + + public Revision? ChangedFrom { get; } + + public Revision ChangedTo { get; } +} diff --git a/src/Core/Events/AppCreatedEvent.cs b/src/Core/Events/AppCreatedEvent.cs new file mode 100644 index 000000000..e17a17007 --- /dev/null +++ b/src/Core/Events/AppCreatedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class AppCreatedEvent : DomainEvent +{ + public AppCreatedEvent(App app) + { + App = app; + } + + public App App { get; } +} diff --git a/src/Core/Events/AppDeletedEvent.cs b/src/Core/Events/AppDeletedEvent.cs new file mode 100644 index 000000000..88cc8f188 --- /dev/null +++ b/src/Core/Events/AppDeletedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class AppDeletedEvent : DomainEvent +{ + public AppDeletedEvent(App app) + { + App = app; + } + + public App App { get; } +} diff --git a/src/Core/Events/ChannelCreatedEvent.cs b/src/Core/Events/ChannelCreatedEvent.cs new file mode 100644 index 000000000..0aa312232 --- /dev/null +++ b/src/Core/Events/ChannelCreatedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class ChannelCreatedEvent : DomainEvent +{ + public ChannelCreatedEvent(Channel channel) + { + Channel = channel; + } + + public Channel Channel { get; } +} diff --git a/src/Core/Events/ChannelDeletedEvent.cs b/src/Core/Events/ChannelDeletedEvent.cs new file mode 100644 index 000000000..8723491e7 --- /dev/null +++ b/src/Core/Events/ChannelDeletedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class ChannelDeletedEvent : DomainEvent +{ + public ChannelDeletedEvent(Channel channel) + { + Channel = channel; + } + + public Channel Channel { get; } +} diff --git a/src/Core/Events/DomainCreatedEvent.cs b/src/Core/Events/DomainCreatedEvent.cs new file mode 100644 index 000000000..6275de7e7 --- /dev/null +++ b/src/Core/Events/DomainCreatedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class DomainCreatedEvent : DomainEvent +{ + public DomainCreatedEvent(Domain domain) + { + Domain = domain; + } + + public Domain Domain { get; } +} diff --git a/src/Core/Events/DomainDeletedEvent.cs b/src/Core/Events/DomainDeletedEvent.cs new file mode 100644 index 000000000..995fbdc42 --- /dev/null +++ b/src/Core/Events/DomainDeletedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class DomainDeletedEvent : DomainEvent +{ + public DomainDeletedEvent(Domain domain) + { + Domain = domain; + } + + public Domain Domain { get; } +} diff --git a/src/Core/Events/EnvironmentVariableCreatedEvent.cs b/src/Core/Events/EnvironmentVariableCreatedEvent.cs new file mode 100644 index 000000000..096a82049 --- /dev/null +++ b/src/Core/Events/EnvironmentVariableCreatedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class EnvironmentVariableCreatedEvent : DomainEvent +{ + public EnvironmentVariableCreatedEvent(EnvironmentVariable environmentVariable) + { + EnvironmentVariable = environmentVariable; + } + + public EnvironmentVariable EnvironmentVariable { get; } +} diff --git a/src/Core/Events/EnvironmentVariableDeletedEvent.cs b/src/Core/Events/EnvironmentVariableDeletedEvent.cs new file mode 100644 index 000000000..4f282d6ea --- /dev/null +++ b/src/Core/Events/EnvironmentVariableDeletedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class EnvironmentVariableDeletedEvent : DomainEvent +{ + public EnvironmentVariableDeletedEvent(EnvironmentVariable environmentVariable) + { + EnvironmentVariable = environmentVariable; + } + + public EnvironmentVariable EnvironmentVariable { get; } +} diff --git a/src/Core/Events/RevisionCreatedEvent.cs b/src/Core/Events/RevisionCreatedEvent.cs new file mode 100644 index 000000000..2ec55114b --- /dev/null +++ b/src/Core/Events/RevisionCreatedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class RevisionCreatedEvent : DomainEvent +{ + public RevisionCreatedEvent(Revision revision) + { + Revision = revision; + } + + public Revision Revision { get; } +} diff --git a/src/Core/Events/RevisionDeletedEvent.cs b/src/Core/Events/RevisionDeletedEvent.cs new file mode 100644 index 000000000..591ffd674 --- /dev/null +++ b/src/Core/Events/RevisionDeletedEvent.cs @@ -0,0 +1,11 @@ +namespace Hippo.Core.Events; + +public class RevisionDeletedEvent : DomainEvent +{ + public RevisionDeletedEvent(Revision revision) + { + Revision = revision; + } + + public Revision Revision { get; } +} diff --git a/src/Core/Exceptions/InvalidRevisionRangeRule.cs b/src/Core/Exceptions/InvalidRevisionRangeRule.cs new file mode 100644 index 000000000..b9c922ecb --- /dev/null +++ b/src/Core/Exceptions/InvalidRevisionRangeRule.cs @@ -0,0 +1,9 @@ +namespace Hippo.Core.Exceptions; + +public class InvalidRevisionRangeRuleException : Exception +{ + public InvalidRevisionRangeRuleException(string rule) + : base($"Range rule \"{rule}\" is invalid.") + { + } +} diff --git a/src/Core/_Imports.cs b/src/Core/_Imports.cs new file mode 100644 index 000000000..20166a340 --- /dev/null +++ b/src/Core/_Imports.cs @@ -0,0 +1,5 @@ +global using Hippo.Core.Common; +global using Hippo.Core.Entities; +global using Hippo.Core.Enums; +global using Hippo.Core.Events; +global using Hippo.Core.Exceptions; diff --git a/src/Hippo/Api/ApplicationController.cs b/src/Hippo/Api/ApplicationController.cs deleted file mode 100644 index 8d4ea5937..000000000 --- a/src/Hippo/Api/ApplicationController.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Threading.Tasks; -using Hippo.Controllers; -using Hippo.Messages; -using Hippo.Models; -using Hippo.Repositories; -using Hippo.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Hippo.Api; - -/// -/// ApplicationController providers an API to create Hippo Applications -/// -[Route("api/[controller]")] -[ApiController] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class ApplicationController : ApplicationBaseController -{ - /// - /// Initializes a new instance of the class. - /// - /// Iunitofwork instance - /// UserManager instance - /// - /// ILogger Instance - public ApplicationController(IUnitOfWork unitOfWork, UserManager userManager, ITaskQueue channelsToReschedule, ILogger logger) - : base(unitOfWork, userManager, channelsToReschedule, logger, EventOrigin.API) - { - } - - /// - /// Creates a new Hippo Application - /// - /// - /// Sample request: - /// - /// POST /api/application - /// { - /// "applicationName": "My excellent new application", - /// "storageId": "contoso/weather" - /// } - /// - /// - /// The application details. - /// Details of the newly created Hippo Application. - /// Returns the newly created Application details - /// The request is invalid - /// An error occured in the server when processing the request - [HttpPost(Name = "CreateHippoApplication")] - [ProducesResponseType(typeof(CreateApplicationResponse), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - [Consumes("application/json")] - [Produces("application/json")] - public async Task> New([FromBody] CreateApplicationRequest request) - { - try - { - TraceMethodEntry(WithArgs(request)); - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - var result = await CreateApplication(request); - - if (result.Result != null) - { - return result.Result; - } - - var application = result.Value; - TraceMessage($"Successfully Created Application Id: {application.Id}"); - - var response = new CreateApplicationResponse - { - Id = application.Id, - ApplicationName = application.Name, - StorageId = application.StorageId, - }; - return Created(response); - - } - catch (Exception ex) - { - _logger.LogError(ex, $"Exception Creating Application Name:{request.ApplicationName} StorageId: {request.StorageId}"); - return StatusCode(StatusCodes.Status500InternalServerError); - } - } -} diff --git a/src/Hippo/Api/ChannelController.cs b/src/Hippo/Api/ChannelController.cs deleted file mode 100644 index 19d653eaf..000000000 --- a/src/Hippo/Api/ChannelController.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Threading.Tasks; -using Hippo.Controllers; -using Hippo.Messages; -using Hippo.Models; -using Hippo.Repositories; -using Hippo.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Hippo.Api; - -/// -/// ChannelController providers an API to create Hippo Channels -/// -[Route("api/[controller]")] -[ApiController] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class ChannelController : ApplicationBaseController -{ - /// - /// Initializes a new instance of the class. - /// - /// Iunitofwork instance - /// - /// ITaskQueue instance - /// ILogger Instance - public ChannelController(IUnitOfWork unitOfWork, UserManager userManager, ITaskQueue taskQueue, ILogger logger) - : base(unitOfWork, userManager, taskQueue, logger, EventOrigin.API) - { - } - - /// - /// Creates a new Hippo ChannelMessage - /// - /// - /// Sample requests: - /// - /// POST /api/channel - /// { - /// "appId": "4208d635-7618-4150-b8a8-bc3205e70e32", - /// "name": "My new ChannelMessage", - /// "revisionSelectionStrategy": "UseRangeRule", - /// "revisionNumber": "1.2.3" - /// } - /// - /// POST /api/channel - /// { - /// "appId": "4208d635-7618-4150-b8a8-bc3205e70e32", - /// "revisionSelectionStrategy": "UseSpecifiedRevision", - /// "fixedToRevision": false, - /// "revisionRange": "~1.2.3" - /// } - /// - /// - /// The channel details. - /// Details of the newly created ChannelMessage. - /// Returns the newly created ApplicationMessage details - /// The request is invalid - /// App was not found with the appId - /// An error occured in the server when processing the request - [HttpPost(Name = "CreateHippoChannel")] - [ProducesResponseType(typeof(Messages.CreateChannelResponse), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - [Consumes("application/json")] - [Produces("application/json")] - public async Task> New([FromBody] CreateChannelRequest request) - { - try - { - TraceMethodEntry(WithArgs(request)); - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - var result = await CreateChannel(request); - - if (result.Result != null) - { - return result.Result; - } - - var channel = result.Value; - var response = new CreateChannelResponse() - { - Id = channel.Id, - AppId = channel.Application.Id, - Name = request.Name, - Domain = channel.Domain.Name, - RevisionNumber = request.RevisionNumber, - RevisionRange = request.RevisionRange, - RevisionSelectionStrategy = request.RevisionSelectionStrategy - }; - return Created(response); - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception Creating Channel"); - return StatusCode(StatusCodes.Status500InternalServerError); - } - } - - /// - /// Gets a Hippo ChannelMessage by ChannelMessage Id - /// - /// - /// Sample requests: - /// - /// Get /api/channel/4208d635-7618-4150-b8a8-bc3205e70e32 - /// - /// - /// - /// The channel id. - /// Details of the ChannelMessage. - /// Returns the newly created ApplicationMessage details - /// The request is invalid - /// App was not found with the appId - /// An error occured in the server when processing the request - [HttpGet(Name = "GetHippoChannelById")] - [ProducesResponseType(typeof(Messages.GetChannelResponse), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - [Produces("application/json")] - public ActionResult GetById(string id) - { - try - { - TraceMethodEntry(WithArgs(id)); - if (!Guid.TryParse(id, out var guid)) - { - return BadRequest($"{id} is not a valid Identifier"); - } - - var channel = _unitOfWork.Channels.GetChannelById(guid); - LogIfNotFound(channel, id); - if (channel == null) - { - return NotFound(); - } - var response = new Messages.GetChannelResponse - { - AppId = channel.Application.Id, - RevisionSelectionStrategy = channel.RevisionSelectionStrategy, - Name = channel.Name, - RevisionNumber = channel.SpecifiedRevision.RevisionNumber, - RevisionRange = channel.RangeRule - }; - return Ok(response); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Exception Getting Channel By Id {id}"); - return StatusCode(StatusCodes.Status500InternalServerError); - } - } -} diff --git a/src/Hippo/Config/ChannelConfig.cs b/src/Hippo/Config/ChannelConfig.cs deleted file mode 100644 index be988d41c..000000000 --- a/src/Hippo/Config/ChannelConfig.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using Microsoft.Extensions.Primitives; -using Yarp.ReverseProxy.Configuration; - -namespace Hippo.Config; - -public class ChannelConfig : IProxyConfig -{ - private readonly CancellationTokenSource _cancellationTokenSource = new(); - public IReadOnlyList Routes { get; } - public IReadOnlyList Clusters { get; } - public IChangeToken ChangeToken { get; } - - public ChannelConfig(IReadOnlyList routes = null, IReadOnlyList clusters = null) - { - Routes = routes ?? new List(); - Clusters = clusters ?? new List(); - ChangeToken = new CancellationChangeToken(_cancellationTokenSource.Token); - } - - internal void SignalChange() - { - _cancellationTokenSource.Cancel(); - } - -} diff --git a/src/Hippo/Config/ChannelConfigurationProvider.cs b/src/Hippo/Config/ChannelConfigurationProvider.cs deleted file mode 100644 index 6e12550c2..000000000 --- a/src/Hippo/Config/ChannelConfigurationProvider.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using Hippo.Models; -using Microsoft.Extensions.Configuration; - -namespace Hippo.Config; - -public class ChannelConfigurationProvider : ConfigurationProvider, IChannelConfigurationProvider -{ - const string ConfigPrefix = "Wagi:Bindles"; - const string DefaultEndpointKey = "Kestrel:Endpoints:Http:Url"; - const string DefaultEndpointValue = "http://127.0.0.1:0"; - private readonly Dictionary _channelDetailsDictionary = new(); - - public ChannelConfigurationProvider() - { - // Kestrel needs at least one endpoint to listen on this is removed as soon as the first channel is added. - Data[DefaultEndpointKey] = DefaultEndpointValue; - } - - public void SetBindleServer(string bindleServer) - => Data["Wagi:BindleServer"] = bindleServer; - - public void AddChannel(Channel channel, string listenAddress) - { - _channelDetailsDictionary[channel.Id] = new ChannelDetails(channel, listenAddress); - - if (!Uri.TryCreate(listenAddress, UriKind.Absolute, out var uri)) - { - throw new ArgumentException($"Listen Address for Channel Id {channel.Id} Name: {channel.Name} ListenAddress: {listenAddress}."); - } - - var listenAddressKey = $"Kestrel:Endpoints:{channel.Id}:Url"; - Data[listenAddressKey] = listenAddress; - var bindleConfigPrefix = $"{ConfigPrefix}:{channel.Id}"; - var bindleKey = $"{bindleConfigPrefix}:Name"; - var bindleValue = $"{channel.Application.StorageId}/{channel.ActiveRevision.RevisionNumber}"; - Data[bindleKey] = bindleValue; - var hostNamesKey = $"{bindleConfigPrefix}:Hostnames:"; - var host = $"{uri.Host}:{uri.Port}"; - Data[hostNamesKey] = host; - var routeKey = $"{bindleConfigPrefix}:Route"; - Data[routeKey] = "/"; - - foreach (var envVar in channel.GetEnvironmentVariables()) - { - var envKey = $"{bindleConfigPrefix}:Environment:{envVar.Key}"; - Data[envKey] = envVar.Value; - } - - Data.Remove(DefaultEndpointKey); - OnReload(); - } - - public override void Load() - { - OnReload(); - } - - public void RemoveChannel(Channel channel) - { - var channelDetails = _channelDetailsDictionary[channel.Id]; - var listenAddress = channelDetails.listenAddress; - var listenAddressKey = $"Kestrel:Endpoints:{channel.Id}:Url"; - Data.Remove(listenAddressKey); - var listenPrefix = $"{ConfigPrefix}:{listenAddress}"; - var bindleKey = $"{listenPrefix}:Name"; - Data.Remove(bindleKey); - - foreach (var envVar in channel.GetEnvironmentVariables()) - { - var envKey = $"{listenPrefix}:Environment:{envVar.Key}"; - Data.Remove(envKey); - } - - _channelDetailsDictionary.Remove(channel.Id); - OnReload(); - } -} - -record ChannelDetails(Channel channel, string listenAddress); diff --git a/src/Hippo/Config/ChannelConfigurationSource.cs b/src/Hippo/Config/ChannelConfigurationSource.cs deleted file mode 100644 index ed8563998..000000000 --- a/src/Hippo/Config/ChannelConfigurationSource.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Configuration; - -namespace Hippo.Config; - -public class ChannelConfigurationSource : IConfigurationSource -{ - private readonly ChannelConfigurationProvider _channelConfigurationProvider; - public ChannelConfigurationSource(ChannelConfigurationProvider channelConfigurationProvider) - { - _channelConfigurationProvider = channelConfigurationProvider; - } - public IConfigurationProvider Build(IConfigurationBuilder builder) => - _channelConfigurationProvider; -} diff --git a/src/Hippo/Config/ConfigurationBuilderExtensions.cs b/src/Hippo/Config/ConfigurationBuilderExtensions.cs deleted file mode 100644 index eb34b2937..000000000 --- a/src/Hippo/Config/ConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.Configuration; - -namespace Hippo.Config; - -public static class ConfigurationBuilderExtensions -{ - public static IConfigurationBuilder AddChannelConfiguration(this IConfigurationBuilder builder, ChannelConfigurationProvider channelConfigurationProvider) - { - return builder.Add(new ChannelConfigurationSource(channelConfigurationProvider)); - } -} diff --git a/src/Hippo/Config/IChannelConfigurationProvider.cs b/src/Hippo/Config/IChannelConfigurationProvider.cs deleted file mode 100644 index 683d39f44..000000000 --- a/src/Hippo/Config/IChannelConfigurationProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Hippo.Models; - -namespace Hippo.Config; - -public interface IChannelConfigurationProvider -{ - public void SetBindleServer(string bindleServer); - public void AddChannel(Channel channel, string listenAddress); - public void RemoveChannel(Channel channel); -} diff --git a/src/Hippo/Controllers/AccountController.cs b/src/Hippo/Controllers/AccountController.cs deleted file mode 100644 index efa39b595..000000000 --- a/src/Hippo/Controllers/AccountController.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; -using Hippo.Models; -using Hippo.Repositories; -using Hippo.ViewModels; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; - -namespace Hippo.Controllers; - -public class AccountController : BaseController -{ - private readonly SignInManager _signInManager; - private readonly IUnitOfWork _unitOfWork; - private readonly IConfiguration _configuration; - - // TODO: assess logging code for PII/GDPR implications - public AccountController(SignInManager signInManager, IUnitOfWork unitOfWork, IConfiguration configuration, ILogger logger) - : base(logger) - { - this._signInManager = signInManager; - this._unitOfWork = unitOfWork; - this._configuration = configuration; - } - - [HttpGet] - public IActionResult Register() - { - TraceMethodEntry(); - - if (this.User.Identity.IsAuthenticated) - { - return RedirectToAction("Index", "App"); - } - return View(); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Register(AccountRegisterForm form) - { - TraceMethodEntry(WithArgs(form)); - - if (ModelState.IsValid) - { - var account = new Account - { - UserName = form.UserName, - Email = form.Email, - }; - - var result = await _signInManager.UserManager.CreateAsync(account, form.Password); - if (result.Succeeded) - { - _logger.LogTrace($"Register: created user {form.UserName}"); - if (_unitOfWork.Accounts.IsEmpty()) - { - // assign first user as Administrator - var roleResult = await _signInManager.UserManager.AddToRoleAsync(account, "Administrator"); - if (!roleResult.Succeeded) - { - ModelState.AddModelError("", "failed to assign role 'Administrator'"); - foreach (IdentityError error in result.Errors) - { - ModelState.AddModelError("", error.Description); - } - } - else - { - _logger.LogInformation($"Register: {form.UserName} has been granted the 'Administrator' role"); - } - } - return RedirectToAction("Login", "Account"); - } - else - { - _logger.LogWarning($"Register: error(s) creating user {form.UserName}: {string.Join(", ", result.Errors.Select(e => e.Description))}"); - ModelState.AddModelError("", "failed to create account"); - foreach (IdentityError error in result.Errors) - { - ModelState.AddModelError("", error.Description); - } - } - } - else - { - ModelState.AddModelError("", "failed to register"); - } - return View(); - } - - [HttpGet] - public IActionResult Login() - { - TraceMethodEntry(); - - if (this.User.Identity.IsAuthenticated) - { - return RedirectToAction("Index", "App"); - } - return View(); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Login(LoginForm form) - { - TraceMethodEntry(WithArgs(form)); - - if (ModelState.IsValid) - { - var result = await _signInManager.PasswordSignInAsync(form.UserName, form.Password, form.RememberMe, false); - await RecordLoginAttempt(EventOrigin.UI, form.UserName, result); - - if (result.Succeeded) - { - _logger.LogTrace($"Login {form.UserName}: succeeded: {SigninFailureLogMessage(result)}"); - - if (Request.Query.Keys.Contains("ReturnUrl")) - { - Redirect(Request.Query["ReturnUrl"].First()); - } - else - { - return RedirectToAction("Index", "App"); - } - } - else - { - _logger.LogWarning($"Login {form.UserName}: failed: {SigninFailureLogMessage(result)}"); - - if (result.IsNotAllowed) - { - ModelState.AddModelError("", "cannot log in at this time; please contact the administrator"); - } - - if (result.IsLockedOut) - { - ModelState.AddModelError("", "account locked; please contact the administrator"); - } - } - } - else - { - ModelState.AddModelError("", "failed to login"); - } - return View(); - } - - [HttpGet] - public async Task Logout() - { - TraceMethodEntry(); - - await _signInManager.SignOutAsync(); - return RedirectToAction("Index", "App"); - } - - // TODO: This is an API so needs swagger attributes and moving under APIControllers Directory, needs to be co-ordinated with changes to HippoFactory - - [HttpPost] - public async Task CreateToken([FromBody] ApiLoginForm form) - { - TraceMethodEntry(WithArgs(form)); - - if (ModelState.IsValid) - { - var user = await _signInManager.UserManager.FindByNameAsync(form.UserName); - if (user != null) - { - var result = await _signInManager.CheckPasswordSignInAsync(user, form.Password, lockoutOnFailure: false); - await RecordLoginAttempt(EventOrigin.API, form.UserName, result); - - if (result.Succeeded) - { - _logger.LogTrace($"CreateToken {form.UserName}: sign in succeeded"); - - // create the token here - // Claims-based identity is a common way for applications to acquire the identity information they need about users inside their organization, in other organizations, - // and on the Internet. It also provides a consistent approach for applications running on-premises or in the cloud. - // Claims-based identity abstracts the individual elements of identity and access control into two parts: - // - // 1. a notion of claims, and - // 2. the concept of an issuer or an authority - // - // to create a claim you need a time and a value! - var claims = new[] - { - // Sub - name of the subject - which is user email here. - new Claim(JwtRegisteredClaimNames.Sub, user.Email), - // jti - unique string that is representative of each token so using a guid - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - // unique name - username of the user mapped to the identity inside the user object - // that is available on every controller and view - new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName) - }; - - // key is the secret used to encrypt the token. some parts of the token aren't encrypted but other parts are. - // credentials, who it is tied to and exploration etc are encrypted. - // information about the claims, about the individual etc aren't encrypted. - // use a natural string for a string and encode it to bytes. - // read from configuration json - keep changing/or fetch from another source. - // the trick here is that the key needs to be accessible for the application - // also needs to be replaceable by the people setting up your system. - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); - - // new credentials required. create it using the key you just created in combination with a - // security algorithm. - var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - var token = new JwtSecurityToken(_configuration["Jwt:Issuer"], // the creator of the token - _configuration["Jwt:Audience"], // who can use the token - claims, - expires: DateTime.UtcNow.AddMinutes(30), - signingCredentials: credentials); - - var results = new - { - token = new JwtSecurityTokenHandler().WriteToken(token), - expiration = token.ValidTo - }; - - return Created(results); - } - else - { - _logger.LogWarning($"CreateToken {form.UserName}: sign in failed: {SigninFailureLogMessage(result)}"); - } - } - } - return BadRequest(); - } - - private static string SigninFailureLogMessage(Microsoft.AspNetCore.Identity.SignInResult result) - { - var reasons = new List(); - if (result.IsNotAllowed) - { - reasons.Add("not allowed"); - } - if (result.IsLockedOut) - { - reasons.Add("locked out"); - } - if (result.RequiresTwoFactor) - { - reasons.Add("needs 2FA"); - } - return string.Join(",", reasons); - } - - private async Task RecordLoginAttempt(EventOrigin source, string userName, Microsoft.AspNetCore.Identity.SignInResult result) - { - if (result.Succeeded) - { - await _unitOfWork.EventLog.LoginSucceeded(source, userName); - } - else - { - await _unitOfWork.EventLog.LoginFailed(source, userName, SigninFailureLogMessage(result)); - } - await _unitOfWork.SaveChanges(); - } -} diff --git a/src/Hippo/Controllers/AdminController.cs b/src/Hippo/Controllers/AdminController.cs deleted file mode 100644 index af41a6675..000000000 --- a/src/Hippo/Controllers/AdminController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Hippo.Controllers; - -[Authorize(Roles = "Administrator")] -public class AdminController : Controller -{ - [HttpGet] - public IActionResult Terminal() - { - return View(); - } -} diff --git a/src/Hippo/Controllers/AppController.cs b/src/Hippo/Controllers/AppController.cs deleted file mode 100644 index e398ff2a6..000000000 --- a/src/Hippo/Controllers/AppController.cs +++ /dev/null @@ -1,501 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Hippo.Models; -using Hippo.Repositories; -using Hippo.Tasks; -using Hippo.ViewModels; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace Hippo.Controllers; - -[Authorize] -public class AppController : ApplicationBaseController -{ - public AppController(IUnitOfWork unitOfWork, UserManager userManager, ITaskQueue channelsToReschedule, ILogger logger) - : base(unitOfWork, userManager, channelsToReschedule, logger, EventOrigin.UI) - { - } - - [HttpGet] - public IActionResult Index() - { - TraceMethodEntry(); - - return View(_unitOfWork.Applications.ListApplications()); - } - - [HttpGet] - public IActionResult Details(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var a = _unitOfWork.Applications.GetApplicationById(id); - LogIfNotFound(a, id); - - if (a == null) - { - return NotFound(); - } - - // TODO: some of this logic should be in the viewmodel - var vm = new AppDetails - { - Application = a, - Channels = a.Channels.ToList(), - Revisions = a.Revisions.OrderByDescending(r => r.OrderKey()).ToList(), - RecentActivity = _unitOfWork.EventLog.GetRecentByApplication(a, 20).ToList() - }; - - return View(vm); - } - - [HttpGet] - public IActionResult New() - { - TraceMethodEntry(); - return View(); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task New(AppNewForm form) - { - TraceMethodEntry(WithArgs(form)); - - if (!ModelState.IsValid) - { - return View(form); - } - - var result = await CreateApplication(form); - - if (result.Result != null) - { - return result.Result; - } - - var application = result.Value; - _logger.LogInformation($"New: application {application.Id} created"); - return RedirectToAction(nameof(Index)); - } - - [HttpGet] - public IActionResult Edit(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var a = _unitOfWork.Applications.GetApplicationById(id); - LogIfNotFound(a, id); - - if (a == null) - { - return NotFound(); - } - - var vm = new AppEditForm - { - Id = a.Id, - Name = a.Name, - StorageId = a.StorageId, - Collaborators = string.Join("; ", a.SafeCollaborations().Select(c => c.User.UserName)), - }; - return View(vm); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Edit(Guid id, [Bind("Id,Name,StorageId,Collaborators")] AppEditForm form) - { - TraceMethodEntry(WithArgs(id, form)); - - if (id != form.Id) - { - LogIdMismatch("application", id, form.Id); - return NotFound(); - } - - if (ModelState.IsValid) - { - try - { - var a = _unitOfWork.Applications.GetApplicationById(id); - LogIfNotFound(a, id); - - if (a == null) - { - return NotFound(); - } - - var storageIdChanged = (a.StorageId != form.StorageId); - - a.Name = form.Name; - a.StorageId = form.StorageId; - - IReadOnlyList changedChannels = new List(); - - if (storageIdChanged) - { - a.Revisions.Clear(); - foreach (var channel in a.Channels) - { - channel.ActiveRevision = null; - } - changedChannels = new List(a.Channels); - } - - var getCollaborationsTasks = form.ParseCollaborators().Select(async c => new Collaboration { Application = a, User = await _userManager.FindByNameAsync(c) }); - var collaborations = await Task.WhenAll(getCollaborationsTasks); - a.Collaborations = collaborations.ToList(); // Yes, you need to convert it to a List or you get weird errors on save - - _unitOfWork.Applications.Update(a); - await _unitOfWork.SaveChanges(); - - if (storageIdChanged) - { - await QueueChangedChannelNotifications(a, changedChannels); - } - } - catch (DbUpdateConcurrencyException) - { - if (!_unitOfWork.Applications.ApplicationExistsById(form.Id)) - { - _logger.LogWarning($"Edit: concurrency error updating {form.Id}: no longer exists"); - return NotFound(); - } - else - { - _logger.LogError($"Edit: concurrency error updating {form.Id}"); - throw; - } - } - return RedirectToAction(nameof(Index)); - } - return View(form); - } - - [HttpGet] - public IActionResult Delete(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var a = _unitOfWork.Applications.GetApplicationById(id); - LogIfNotFound(a, id); - - if (a == null) - { - return NotFound(); - } - return View(a); - } - - [HttpPost, ActionName("Delete")] - [ValidateAntiForgeryToken] - public async Task DeleteConfirmed(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - _unitOfWork.Applications.DeleteApplicationById(id); - await _unitOfWork.SaveChanges(); - return RedirectToAction(nameof(Index)); - } - - [HttpGet] - public IActionResult NewChannel(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var a = _unitOfWork.Applications.GetApplicationById(id); - var vm = new AppNewChannelForm - { - Id = a.Id, - RevisionSelectionStrategies = Converters.EnumValuesAsSelectList(), - Revisions = a.Revisions.AsSelectList(r => r.RevisionNumber), - }; - return View(vm); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task NewChannel(Guid id, AppNewChannelForm form) - { - TraceMethodEntry(WithArgs(id, form)); - - if (id != form.Id) - { - LogIdMismatch("application", id, form.Id); - return NotFound(); - } - - if (!ModelState.IsValid) - { - return RedirectToAction(nameof(Index)); - } - - var result = await CreateChannel(form); - - if (result.Result != null) - { - return result.Result; - } - - var channel = result.Value; - _logger.LogInformation($"NewChannel: application {form.Id} channel {channel.Id} now at revision {channel.ActiveRevision?.RevisionNumber ?? "[none]"}"); - _logger.LogInformation($"NewChannel: serving on port {channel.PortID + Channel.EphemeralPortRange}"); - return RedirectToAction(nameof(Index)); - } - - private static IEnumerable ParseEnvironmentVariables(string text) - { - // TODO: assumes validation in web form - should not assume this - if (string.IsNullOrWhiteSpace(text)) - { - return Enumerable.Empty(); - } - - return text.Split('\n', ';') - .Select(e => e.Trim()) - .Select(e => e.Split('=')) - .Select(bits => new EnvironmentVariable { Key = bits[0], Value = bits[1] }); - } - - [HttpGet] - public IActionResult EditChannel(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var channel = _unitOfWork.Channels.GetChannelById(id); - var application = _unitOfWork.Applications.GetApplicationById(channel.Application.Id); // To get the revisions - var vm = new AppEditChannelForm - { - ChannelId = channel.Id, - ApplicationId = application.Id, - ChannelName = channel.Name, - RevisionSelectionStrategies = Converters.EnumValuesAsSelectList(), - SelectedRevisionSelectionStrategy = Enum.GetName(channel.RevisionSelectionStrategy), - Revisions = application.Revisions.AsSelectList(r => r.RevisionNumber), - EnvironmentVariables = string.Join('\n', channel.GetEnvironmentVariables().Select(e => $"{e.Key}={e.Value}")), - Domain = channel.Domain?.Name, - }; - if (channel.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision) - { - vm.SelectedRevisionNumber = channel.SpecifiedRevision?.RevisionNumber; - } - else if (channel.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseRangeRule) - { - vm.SelectedRevisionRule = channel.RangeRule; - } - return View(vm); - } - - [HttpPost] - [ValidateAntiForgeryToken] - // NOTE: the arg has to be called id not channelId, and yes it is confusing - public async Task EditChannel(Guid id, AppEditChannelForm form) - { - TraceMethodEntry(WithArgs(id, form)); - - if (id != form.ChannelId) - { - LogIdMismatch("channel", id, form.ChannelId); - return NotFound(); - } - - if (ModelState.IsValid) - { - var channel = _unitOfWork.Channels.GetChannelById(form.ChannelId); - var application = _unitOfWork.Applications.GetApplicationById(form.ApplicationId); - - if (application == null || channel == null) - { - LogIfNotFound(application, form.ApplicationId); - LogIfNotFound(channel, form.ChannelId); - return NotFound(); - } - - if (application.Id != channel.Application.Id) - { - LogIdMismatch("application", channel.Application.Id, form.ApplicationId); - return NotFound(); - } - - if (form.SelectedRevisionSelectionStrategy == Enum.GetName(ChannelRevisionSelectionStrategy.UseSpecifiedRevision)) - { - var revision = _unitOfWork.Revisions.GetRevisionByNumber(application, form.SelectedRevisionNumber); - if (revision == null) - { - LogIfNotFound(revision, form.SelectedRevisionNumber); - return NotFound(); - } - channel.RevisionSelectionStrategy = ChannelRevisionSelectionStrategy.UseSpecifiedRevision; - channel.SpecifiedRevision = revision; - } - else if (form.SelectedRevisionSelectionStrategy == Enum.GetName(ChannelRevisionSelectionStrategy.UseRangeRule)) - { - var rule = form.SelectedRevisionRule; - if (string.IsNullOrWhiteSpace(rule)) - { - _logger.LogError("EditChannel: empty rule"); - return BadRequest("rule was empty"); // TODO: this is a terrible way of handling it; await Ronan - } - channel.RevisionSelectionStrategy = ChannelRevisionSelectionStrategy.UseRangeRule; - channel.RangeRule = rule; - } - else - { - _logger.LogError("EditChannel: no strategy"); - return BadRequest("no strategy"); // TODO: this is a terrible way of handling it; await Ronan - } - - var revChange = channel.ReevaluateActiveRevision(); - - // TODO: SO MUCH DEDUPLICATION - - // TODO: should probably only update the entities if stuff changes, otherwise - // we will leak many identical rows into the database - var environmentVariables = ParseEnvironmentVariables(form.EnvironmentVariables).ToList(); - - channel.Configuration = new Configuration - { - EnvironmentVariables = environmentVariables, - }; - channel.Domain = new Domain { Name = form.Domain }; - - // TODO: not sure if this is needed - foreach (var ev in environmentVariables) - { - ev.Configuration = channel.Configuration; - } - - await _unitOfWork.EventLog.ChannelEdited(_eventSource, channel); - - if (revChange != null) - { - await _unitOfWork.EventLog.ChannelRevisionChanged(_eventSource, channel, revChange.ChangedFrom, "channel reconfigured"); - } - - await _unitOfWork.SaveChanges(); - await _channelsToReschedule.Enqueue(new ChannelReference(application.Id, channel.Id), CancellationToken.None); - - _logger.LogInformation($"EditChannel: application {form.ApplicationId} channel {channel.Id} now at revision {channel.ActiveRevision?.RevisionNumber ?? "[none]"}"); - _logger.LogInformation($"EditChannel: serving on port {channel.PortID + Channel.EphemeralPortRange}"); - return RedirectToAction(nameof(Index)); - } - - return View(form); - } - - [HttpGet] - public IActionResult DeleteChannel(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var channel = _unitOfWork.Channels.GetChannelById(id); - LogIfNotFound(channel, id); - - if (channel == null) - { - return NotFound(); - } - return View(channel); - } - - [HttpPost, ActionName("DeleteChannel")] - [ValidateAntiForgeryToken] - public async Task DeleteChannelConfirmed(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var channel = _unitOfWork.Channels.GetChannelById(id); - LogIfNotFound(channel, id); - - if (channel == null) - { - return NotFound(); - } - - var channelName = channel.Name; - var application = channel.Application; - - _unitOfWork.Channels.DeleteChannelById(id); - await _unitOfWork.EventLog.ChannelDeleted(EventOrigin.UI, id, application, channelName); - await _unitOfWork.SaveChanges(); - - _logger.LogInformation($"DeleteChannelConfirmed: deleted channel {id}"); - return RedirectToAction(nameof(Index)); - } - - [HttpGet] - public IActionResult RegisterRevision(Guid id) - { - TraceMethodEntry(WithArgs(id)); - - var a = _unitOfWork.Applications.GetApplicationById(id); - var vm = new AppRegisterRevisionForm - { - Id = a.Id, - }; - return View(vm); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RegisterRevision(Guid id, AppRegisterRevisionForm form) - { - TraceMethodEntry(WithArgs(id, form)); - - if (id != form.Id) - { - LogIdMismatch("application", id, form.Id); - return NotFound(); - } - - if (ModelState.IsValid) - { - var application = _unitOfWork.Applications.GetApplicationById(id); - - application.Revisions.Add(new Revision { RevisionNumber = form.RevisionNumber }); - - var changes = application.ReevaluateActiveRevisions(); - foreach (var change in changes) - { - await _unitOfWork.EventLog.ChannelRevisionChanged(_eventSource, change.Channel, change.ChangedFrom, "revision registered"); - } - await _unitOfWork.SaveChanges(); - - await QueueChangedChannelNotifications(application, changes); - - _logger.LogInformation($"RegisterRevision: application {form.Id} registered {form.RevisionNumber}"); - return RedirectToAction(nameof(Index)); - } - - return View(form); - } - - private async Task QueueChangedChannelNotifications(Application application, IReadOnlyList changes) => - await QueueChangedChannelNotifications(application, changes.Select(c => c.Channel)); - - private async Task QueueChangedChannelNotifications(Application application, IEnumerable changedChannels) - { - // TODO: deduplicate with RevisionController - var queueRescheduleTasks = changedChannels.Select(channel => - _channelsToReschedule.Enqueue(new ChannelReference(application.Id, channel.Id), CancellationToken.None) - ); - - try - { - await Task.WhenAll(queueRescheduleTasks); - } - catch (Exception e) - { - _logger.LogError($"RegisterRevision: failed to queue channel rescheduling for one or more of {String.Join(",", changedChannels.Select(c => c.Name))}: {e}"); - throw; - } - } -} diff --git a/src/Hippo/Controllers/ApplicationControllerCore.cs b/src/Hippo/Controllers/ApplicationControllerCore.cs deleted file mode 100644 index e2c303d09..000000000 --- a/src/Hippo/Controllers/ApplicationControllerCore.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Hippo.Models; -using Hippo.OperationData; -using Hippo.Repositories; -using Hippo.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Hippo.Controllers; - -public abstract class ApplicationBaseController : BaseController -{ - private protected readonly IUnitOfWork _unitOfWork; - private protected readonly UserManager _userManager; - private protected readonly ITaskQueue _channelsToReschedule; - private protected readonly EventOrigin _eventSource; - - protected ApplicationBaseController(IUnitOfWork unitOfWork, UserManager userManager, ITaskQueue channelsToReschedule, ILogger logger, EventOrigin eventSource) - : base(logger) - { - _unitOfWork = unitOfWork; - _userManager = userManager; - _channelsToReschedule = channelsToReschedule; - _eventSource = eventSource; - } - - protected async Task> CreateApplication(ICreateApplicationParameters request) - { - var applicationId = Guid.NewGuid(); - var application = new Application - { - Id = applicationId, - Name = request.ApplicationName, - StorageId = request.StorageId, - Owner = await _userManager.FindByNameAsync(User.Identity.Name), - }; - - await _unitOfWork.Applications.AddNew(application); - await _unitOfWork.SaveChanges(); - - return application; - } - - protected async Task> CreateChannel(ICreateChannelParameters request) - { - var application = _unitOfWork.Applications.GetApplicationById(request.ApplicationId); - if (application == null) - { - LogIfNotFound(application, request.ApplicationId); - return NotFound(); - } - // TODO: tidier - var revision = request.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision ? - _unitOfWork.Revisions.GetRevisionByNumber(application, request.RevisionNumber) : - null; - if (request.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision && revision == null) - { - LogIfNotFound(revision, request.RevisionNumber); - return BadRequest($"Cannot create a channel at revision {request.RevisionNumber} as bindle {application.StorageId}/{request.RevisionNumber} does not exist or is not registered"); - } - - // Set up ancillary entites - var domain = new Domain - { - Name = request.DomainName - }; - var configuration = new Configuration - { - EnvironmentVariables = request.EnvironmentVariables.Select(kvp => new EnvironmentVariable { Key = kvp.Key, Value = kvp.Value }).ToList() - }; - - // The channel itself - var channelId = Guid.NewGuid(); - var channel = new Models.Channel - { - Id = channelId, - Application = application, - Name = request.ChannelName, - Domain = domain, - Configuration = configuration, - RevisionSelectionStrategy = request.RevisionSelectionStrategy, - RangeRule = request.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseRangeRule ? request.RangeRule : "", - SpecifiedRevision = request.RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision ? revision : null, - }; - - // Wire up backlinks - // TODO: not sure if this is needed but in-memory database is not great at it - application.Channels.Add(channel); - foreach (var ev in configuration.EnvironmentVariables) - { - ev.Configuration = configuration; - } - - // Finalise - channel.ReevaluateActiveRevision(); - - await _unitOfWork.Channels.AddNew(channel); - await _unitOfWork.EventLog.ChannelCreated(_eventSource, channel); - await _unitOfWork.EventLog.ChannelRevisionChanged(_eventSource, channel, "(none)", "channel created"); - await _unitOfWork.SaveChanges(); - - await _channelsToReschedule.Enqueue(new ChannelReference(channel.Application.Id, channel.Id), CancellationToken.None); - - return channel; - } -} diff --git a/src/Hippo/Controllers/BaseController.cs b/src/Hippo/Controllers/BaseController.cs deleted file mode 100644 index 5990b6d43..000000000 --- a/src/Hippo/Controllers/BaseController.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; -using Hippo.Logging; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Hippo.Controllers; - -public abstract class BaseController : Controller -{ - private protected readonly ILogger _logger; - - protected BaseController(ILogger logger) - { - _logger = logger; - } - - protected void LogIfNotFound(T entity, object entityId, [CallerMemberName] string methodName = null) - { - if (entity == null) - { - var entityType = typeof(T).Name.ToUpperInvariant(); - _logger.LogWarning($"{methodName}: {entityType} {entityId} not found"); - } - } - - protected void TraceMessage(string message, [CallerMemberName] string methodName = null) - { - _logger.LogTrace($"{methodName}: {message}"); - } - - protected void LogIdMismatch(string objectType, Guid expectedId, Guid formId, [CallerMemberName] string methodName = null) - { - _logger.LogWarning($"{methodName}: ${objectType} ID {formId} did not match expected ID {expectedId}"); - } - - protected void TraceMethodEntry([CallerMemberName] string methodName = null) - { - TraceMethodEntry(WithArgs(Array.Empty()), methodName); - } - - protected void TraceMethodEntry(MethodArgs args, [CallerMemberName] string methodName = null) - { - var argsText = args.IsEmpty ? "" : $" with args ({args.Format()})"; - var modelStateText = (ModelState == null || ModelState.IsValid) ? "" : " [model state: invalid]"; - _logger.LogTrace($"{methodName}: entered{argsText}{modelStateText}"); - } - - protected CreatedResult Created(object value) => new("", value); - - protected static MethodArgs WithArgs(params object[] args) - { - return new MethodArgs(args); - } - - protected struct MethodArgs - { - private readonly object[] _args; - public MethodArgs(object[] args) => _args = args; - public bool IsEmpty => _args == null || _args.Length == 0; - - public string Format() - { - if (IsEmpty) - { - return "no args"; - } - - return string.Join(", ", _args.Select(FormatOne)); - } - - public static string FormatOne(object arg) - { - return arg switch - { - null => "null", - ITraceable t => t.FormatTrace(), - _ => arg.ToString(), - }; - } - } -} diff --git a/src/Hippo/Controllers/RevisionController.cs b/src/Hippo/Controllers/RevisionController.cs deleted file mode 100644 index 838c610e4..000000000 --- a/src/Hippo/Controllers/RevisionController.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Hippo.Messages; -using Hippo.Models; -using Hippo.Repositories; -using Hippo.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Hippo.Controllers; - -// TODO: This seems to be an API controller so needs swagger attributes and moving under APIControllers Directory - -[Route("api/[Controller]")] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class RevisionController : BaseController -{ - private readonly IUnitOfWork _unitOfWork; - private readonly ITaskQueue _channelsToReschedule; - - public RevisionController(IUnitOfWork unitOfWork, ITaskQueue channelsToReschedule, ILogger logger) - : base(logger) - { - this._unitOfWork = unitOfWork; - this._channelsToReschedule = channelsToReschedule; - } - - [HttpPost] - public async Task New([FromBody] RegisterRevisionRequest request) - { - TraceMethodEntry(WithArgs(request)); - - if (ModelState.IsValid) - { - var apps = FindApps(); - var changes = new List(); - - foreach (var app in apps) - { - // TODO: less worse handling of duplicate version - app.Revisions.Add(new Revision - { - RevisionNumber = request.RevisionNumber, - }); - - var appChannelChanges = app.ReevaluateActiveRevisions(); - changes.AddRange(appChannelChanges); - } - - foreach (var change in changes) - { - await _unitOfWork.EventLog.ChannelRevisionChanged(EventOrigin.API, change.Channel, change.ChangedFrom, "registered revision"); - } - - await _unitOfWork.SaveChanges(); - - var changedChannels = changes.Select(c => c.Channel).ToList().AsReadOnly(); - - var queueRescheduleTasks = changedChannels.Select(c => - _channelsToReschedule.Enqueue(new ChannelReference(c.Application.Id, c.Id), CancellationToken.None) - ); - - try - { - await Task.WhenAll(queueRescheduleTasks); - } - catch (Exception e) - { - _logger.LogError($"New: failed to queue channel rescheduling for one or more of {String.Join(",", changedChannels.Select(c => c.Name))}: {e}"); - throw; - } - - return apps.Any() ? - Created(null) : - NotFound(); - } - - return NotFound(); - - IList FindApps() - { - if (request.AppId.HasValue) - { - var app = _unitOfWork.Applications.GetApplicationById(request.AppId.Value); - LogIfNotFound(app, request.AppId); - return new[] { app }; - } - else if (request.AppStorageId != null) - { - var apps = _unitOfWork.Applications.ListApplicationsByStorageId(request.AppStorageId).ToList(); - if (apps.Count == 0) - { - _logger.LogDebug($"Register Revision: no apps found for {request.AppStorageId}"); - } - return apps; - } - else - { - _logger.LogWarning($"Register Revision: neither app id nor storage id provided"); - return Array.Empty(); - } - - } - } -} diff --git a/src/Hippo/Extensions/ChannelConfigProviderExtensions.cs b/src/Hippo/Extensions/ChannelConfigProviderExtensions.cs deleted file mode 100644 index 504fd4f8f..000000000 --- a/src/Hippo/Extensions/ChannelConfigProviderExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Hippo.Proxies; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Yarp.ReverseProxy.Configuration; - -namespace Hippo.Extensions; - -/// -/// Extends the IReverseProxyBuilder to support the ChannelConfigProvider -/// -public static class ChannelConfigProviderExtensions -{ - public static IReverseProxyBuilder LoadFromHippoChannels(this IReverseProxyBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(f => f.GetRequiredService()); - builder.Services.AddSingleton(f => f.GetRequiredService()); - return builder; - } -} diff --git a/src/Hippo/Hippo.csproj b/src/Hippo/Hippo.csproj deleted file mode 100644 index a8b1358b6..000000000 --- a/src/Hippo/Hippo.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - net6.0 - Hippo - enable - true - 5 - hippo-server - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - preview - 0.1 - v - - - diff --git a/src/Hippo/Logging/ITraceable.cs b/src/Hippo/Logging/ITraceable.cs deleted file mode 100644 index 36e58e90c..000000000 --- a/src/Hippo/Logging/ITraceable.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hippo.Logging; - -public interface ITraceable -{ - string FormatTrace(); -} diff --git a/src/Hippo/Messages/ApplicationMessage.cs b/src/Hippo/Messages/ApplicationMessage.cs deleted file mode 100644 index e7ee1b383..000000000 --- a/src/Hippo/Messages/ApplicationMessage.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Messages; - -/// -/// Base class for Hippo Application API Messages. -/// -public abstract class ApplicationMessage -{ - /// - /// The name of the Application - /// - /// My Excellent Application - [Required] - public string ApplicationName { get; set; } - - /// - /// This is the ID in Bindle or whatever storage backend is used. It gets composed - /// with a revision ID to get a Bindle ID. - /// - /// For example, the Weather application might have the StorageId contoso/weather. - /// Revision 1.4.0 of the Weather application would then have the bindle id - /// contoso/weather/1.4.0 - /// - /// contoso/weather - [Required] - public string StorageId { get; set; } - - // TODO: validate the storage id format. -} diff --git a/src/Hippo/Messages/ChannelMessage.cs b/src/Hippo/Messages/ChannelMessage.cs deleted file mode 100644 index 5d083c306..000000000 --- a/src/Hippo/Messages/ChannelMessage.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Models; - -namespace Hippo.Messages; - -/// -/// Base class for Hippo Channel API Messages. -/// -public class ChannelMessage -{ - /// - /// The GUID of the Application which the channel belongs to. - /// - /// 4208d635-7618-4150-b8a8-bc3205e70e32 - [Required] - public Guid AppId { get; set; } - - /// - /// The name of the Channel. - /// - /// My Channel - [Required] - public string Name { get; set; } - - /// - /// The name of the Domain being served by the Channel. - /// - /// myapp.hippofactory.io - [Required] - public string Domain { get; set; } - - /// - /// Specifies if the channels is fixed to a revision or uses a revision range. - /// - /// true - [Required] - public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } - - /// - /// The revision number to fix the channel to - ignored if RevisionSelectionStrategy is not UseSpecifiedRevision. - /// - /// 1.2.3 - public string RevisionNumber { get; set; } - - /// - /// The range rule for selecting the active revision - ignored if RevisionSelectionStrategy is not UseRangeRule. - /// - /// ~1.2.3 - public string RevisionRange { get; set; } -} diff --git a/src/Hippo/Messages/CreateApplicationRequest.cs b/src/Hippo/Messages/CreateApplicationRequest.cs deleted file mode 100644 index fc8255fc8..000000000 --- a/src/Hippo/Messages/CreateApplicationRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Hippo.OperationData; -using Hippo.Logging; - -namespace Hippo.Messages; - -/// -/// Request body for a new Hippo Application API. -/// -public class CreateApplicationRequest : ApplicationMessage, ITraceable, ICreateApplicationParameters -{ - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace string - public virtual string FormatTrace() - => $"{GetType().Name}[ApplicationName={ApplicationName}, StorageId={StorageId}]"; -} diff --git a/src/Hippo/Messages/CreateApplicationResponse.cs b/src/Hippo/Messages/CreateApplicationResponse.cs deleted file mode 100644 index 29ffa99bf..000000000 --- a/src/Hippo/Messages/CreateApplicationResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.Messages; - -/// -/// Response body for a new Hippo Application API. -/// -public class CreateApplicationResponse : ApplicationMessage, ITraceable -{ - /// - /// The GUID of the new Application - /// - /// 4208d635-7618-4150-b8a8-bc3205e70e32 - [Required] - public Guid Id { get; set; } - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace string - public string FormatTrace() - => $"{GetType().Name}[ApplicationName={ApplicationName}, ApplicationGUID={Id}], StorageId={StorageId}]"; -} diff --git a/src/Hippo/Messages/CreateChannelRequest.cs b/src/Hippo/Messages/CreateChannelRequest.cs deleted file mode 100644 index b42617f97..000000000 --- a/src/Hippo/Messages/CreateChannelRequest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; -using Hippo.Models; -using Hippo.OperationData; -using Hippo.Rules; - -namespace Hippo.Messages; - -/// -/// Request body for a new ChannelMessage API Request. -/// -public sealed class CreateChannelRequest : ChannelMessage, ITraceable, IValidatableObject, ICreateChannelParameters -{ - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace striing - public string FormatTrace() - => $"{GetType().Name}[Appid={AppId}, Name={Name}, RevisionSelectionStrategy={RevisionSelectionStrategy}, RevisionNumber={RevisionNumber}, RevisionRange={RevisionRange}]"; - - /// - /// IValidatableObject.Validate implementation. - /// - public IEnumerable Validate(ValidationContext validationContext) - { - if (AppId == Guid.Empty) - { - yield return new ValidationResult( - $"AppId is required", - new[] - { - nameof(AppId) - }); - } - - // TODO : Should we validate that the revision exists or if its a range there is at least one revision in the range available? - - if (RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseSpecifiedRevision) - { - if (string.IsNullOrEmpty(RevisionNumber)) - { - yield return new ValidationResult( - $"Revision Number must be specified when fixing a channel to a revision number", - new[] { nameof(RevisionNumber) , - nameof(RevisionSelectionStrategy)}); - } - - if (!SemVer.Version.TryParse(RevisionNumber, out _)) - { - yield return new ValidationResult( - $"Revision Number does not comply with Semantic Versioning version number rules", - new[] { nameof(RevisionNumber) }); - } - - } - - if (RevisionSelectionStrategy == ChannelRevisionSelectionStrategy.UseRangeRule) - { - if (string.IsNullOrEmpty(RevisionRange)) - { - yield return new ValidationResult( - $"Revision Range must be specified when not fixing a channel to a revision number", - new[] { nameof(RevisionRange), - nameof(RevisionSelectionStrategy)}); - } - - - var ruleError = RevisionRangeRule.Validate(RevisionRange); - if (ruleError != null) - { - yield return new ValidationResult( - $"Revision range is not valid rule syntax: {ruleError.Message}", - new[] { nameof(RevisionRange) }); - } - - } - } - - // Adapters for ICreateChannelParameters - Guid ICreateChannelParameters.ApplicationId => AppId; - string ICreateChannelParameters.ChannelName => Name; - string ICreateChannelParameters.DomainName => Domain; - Dictionary ICreateChannelParameters.EnvironmentVariables => new(); - string ICreateChannelParameters.RangeRule => RevisionRange; -} diff --git a/src/Hippo/Messages/CreateChannelResponse.cs b/src/Hippo/Messages/CreateChannelResponse.cs deleted file mode 100644 index 4cfee65e1..000000000 --- a/src/Hippo/Messages/CreateChannelResponse.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.Messages; - -/// -/// Response payload for a new ChannelMessage API Request. -/// -public class CreateChannelResponse : ChannelMessage, ITraceable -{ - /// - /// The GUID of the ChannelMessage which was created. - /// - /// 4208d635-7618-4150-b8a8-bc3205e70e32 - [Required] - public Guid Id { get; set; } - - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace striing - public virtual string FormatTrace() - => $"{GetType().Name}[ChannelId={Id},Appid={AppId}, Name={Name}, RevisionSelectionStrategy={RevisionSelectionStrategy}, RevisionNumber={RevisionNumber}, RevisionRange={RevisionRange}]"; -} diff --git a/src/Hippo/Messages/GetChannelResponse.cs b/src/Hippo/Messages/GetChannelResponse.cs deleted file mode 100644 index 4f14e6895..000000000 --- a/src/Hippo/Messages/GetChannelResponse.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Hippo.Logging; - -namespace Hippo.Messages; - -/// -/// Response body for a get ChannelMessage API Request. -/// -public class GetChannelResponse : ChannelMessage, ITraceable -{ - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace striing - public virtual string FormatTrace() - => $"{GetType().Name}[Appid={AppId}, Name={Name}, RevisionSelectionStrategy={RevisionSelectionStrategy}, RevisionNumber={RevisionNumber}]"; -} diff --git a/src/Hippo/Messages/RegisterRevisionRequest.cs b/src/Hippo/Messages/RegisterRevisionRequest.cs deleted file mode 100644 index 92d6cdcc6..000000000 --- a/src/Hippo/Messages/RegisterRevisionRequest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.Messages; - -/// -/// Request body for Hippo Register Revision API. -/// -public class RegisterRevisionRequest : ITraceable -{ - /// - /// The GUID of the Application which the revision belongs to. - /// - /// 4208d635-7618-4150-b8a8-bc3205e70e32 - public Guid? AppId { get; set; } - - /// - /// This is the ID in Bindle or whatever storage backend is used. It gets composed - /// with a revision ID to get a Bindle ID. - /// - /// For example, the Weather application might have the StorageId contoso/weather. - /// Revision 1.4.0 of the Weather application would then have the bindle id - /// contoso/weather/1.4.0 - /// - /// contoso/weather - public string AppStorageId { get; set; } - - /// - /// The revision number for the new revision. - /// - /// 1.2.3 - [Required] - public string RevisionNumber { get; set; } - - /// - /// ITraceable.FormatTrace implementation. - /// - /// Trace string - public string FormatTrace() - => $"{GetType().Name}[appid={AppId}, stgid={AppStorageId}, rev={RevisionNumber}]"; -} diff --git a/src/Hippo/Migrations/Postgres/20210706011250_Initial.Designer.cs b/src/Hippo/Migrations/Postgres/20210706011250_Initial.Designer.cs deleted file mode 100644 index 34511c891..000000000 --- a/src/Hippo/Migrations/Postgres/20210706011250_Initial.Designer.cs +++ /dev/null @@ -1,605 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210706011250_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("DomainId") - .HasColumnType("uuid"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborators"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210706011250_Initial.cs b/src/Hippo/Migrations/Postgres/20210706011250_Initial.cs deleted file mode 100644 index 2748e14f6..000000000 --- a/src/Hippo/Migrations/Postgres/20210706011250_Initial.cs +++ /dev/null @@ -1,480 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "text", nullable: false), - Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Configuration", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Configuration", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Domains", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Domains", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Keys", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - PublicKey = table.Column(type: "text", nullable: false), - PrivateKey = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Keys", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RoleId = table.Column(type: "text", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "EnvironmentVariables", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - ConfigurationId = table.Column(type: "uuid", nullable: true), - Key = table.Column(type: "text", nullable: false), - Value = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_EnvironmentVariables", x => x.Id); - table.ForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - column: x => x.ConfigurationId, - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "text", nullable: false), - SigningKeyId = table.Column(type: "uuid", nullable: true), - ApplicationId = table.Column(type: "uuid", nullable: true), - UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "boolean", nullable: false), - PasswordHash = table.Column(type: "text", nullable: true), - SecurityStamp = table.Column(type: "text", nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true), - PhoneNumber = table.Column(type: "text", nullable: true), - PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), - TwoFactorEnabled = table.Column(type: "boolean", nullable: false), - LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), - LockoutEnabled = table.Column(type: "boolean", nullable: false), - AccessFailedCount = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUsers_Keys_SigningKeyId", - column: x => x.SigningKeyId, - principalTable: "Keys", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Applications", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "text", nullable: false), - StorageId = table.Column(type: "text", nullable: false), - OwnerId = table.Column(type: "text", nullable: true), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Applications", x => x.Id); - table.ForeignKey( - name: "FK_Applications_AspNetUsers_OwnerId", - column: x => x.OwnerId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "text", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "text", nullable: false), - ProviderKey = table.Column(type: "text", nullable: false), - ProviderDisplayName = table.Column(type: "text", nullable: true), - UserId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "text", nullable: false), - RoleId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "text", nullable: false), - LoginProvider = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - Value = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Revisions", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - ApplicationId = table.Column(type: "uuid", nullable: true), - RevisionNumber = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Revisions", x => x.Id); - table.ForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Channels", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "text", nullable: true), - RevisionSelectionStrategy = table.Column(type: "integer", nullable: false), - SpecifiedRevisionId = table.Column(type: "uuid", nullable: true), - RangeRule = table.Column(type: "text", nullable: true), - ActiveRevisionId = table.Column(type: "uuid", nullable: true), - ApplicationId = table.Column(type: "uuid", nullable: true), - DomainId = table.Column(type: "uuid", nullable: true), - PortID = table.Column(type: "bigint", nullable: false), - ConfigurationId = table.Column(type: "uuid", nullable: true), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_Channels", x => x.Id); - table.ForeignKey( - name: "FK_Channels_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Configuration_ConfigurationId", - column: x => x.ConfigurationId, - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Domains_DomainId", - column: x => x.DomainId, - principalTable: "Domains", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Revisions_ActiveRevisionId", - column: x => x.ActiveRevisionId, - principalTable: "Revisions", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Revisions_SpecifiedRevisionId", - column: x => x.SpecifiedRevisionId, - principalTable: "Revisions", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_Applications_Name", - table: "Applications", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Applications_OwnerId", - table: "Applications", - column: "OwnerId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_SigningKeyId", - table: "AspNetUsers", - column: "SigningKeyId"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Channels_ActiveRevisionId", - table: "Channels", - column: "ActiveRevisionId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_ApplicationId", - table: "Channels", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_ConfigurationId", - table: "Channels", - column: "ConfigurationId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_DomainId", - table: "Channels", - column: "DomainId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_SpecifiedRevisionId", - table: "Channels", - column: "SpecifiedRevisionId"); - - migrationBuilder.CreateIndex( - name: "IX_Domains_Name", - table: "Domains", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_EnvironmentVariables_ConfigurationId", - table: "EnvironmentVariables", - column: "ConfigurationId"); - - migrationBuilder.CreateIndex( - name: "IX_Revisions_ApplicationId", - table: "Revisions", - column: "ApplicationId"); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Applications_AspNetUsers_OwnerId", - table: "Applications"); - - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "Channels"); - - migrationBuilder.DropTable( - name: "EnvironmentVariables"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "Domains"); - - migrationBuilder.DropTable( - name: "Revisions"); - - migrationBuilder.DropTable( - name: "Configuration"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - - migrationBuilder.DropTable( - name: "Applications"); - - migrationBuilder.DropTable( - name: "Keys"); - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.Designer.cs b/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.Designer.cs deleted file mode 100644 index 71d6390df..000000000 --- a/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.Designer.cs +++ /dev/null @@ -1,648 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210712232619_EventHistory")] - partial class EventHistory - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("DomainId") - .HasColumnType("uuid"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborators"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.cs b/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.cs deleted file mode 100644 index a540550ca..000000000 --- a/src/Hippo/Migrations/Postgres/20210712232619_EventHistory.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Postgres -{ - public partial class EventHistory : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "EventLogEntries", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - EventKind = table.Column(type: "integer", nullable: false), - EventSource = table.Column(type: "integer", nullable: false), - Timestamp = table.Column(type: "timestamp without time zone", nullable: false), - ApplicationId = table.Column(type: "uuid", nullable: true), - ChannelId = table.Column(type: "uuid", nullable: true), - UserName = table.Column(type: "text", nullable: true), - Description = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_EventLogEntries", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "EventLogEntries"); - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.Designer.cs b/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.Designer.cs deleted file mode 100644 index db4bb1e55..000000000 --- a/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.Designer.cs +++ /dev/null @@ -1,648 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210722051212_Timestamp")] - partial class Timestamp - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("DomainId") - .HasColumnType("uuid"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborators"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.cs b/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.cs deleted file mode 100644 index dc1dc0dfa..000000000 --- a/src/Hippo/Migrations/Postgres/20210722051212_Timestamp.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Postgres -{ - public partial class Timestamp : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.Designer.cs b/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.Designer.cs deleted file mode 100644 index e5f702d81..000000000 --- a/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.Designer.cs +++ /dev/null @@ -1,687 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210726214502_Collaborations")] - partial class Collaborations - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("DomainId") - .HasColumnType("uuid"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.cs b/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.cs deleted file mode 100644 index 5cabbb8b7..000000000 --- a/src/Hippo/Migrations/Postgres/20210726214502_Collaborations.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Postgres -{ - public partial class Collaborations : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.DropIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.DropColumn( - name: "ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.CreateTable( - name: "collaborations", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - ApplicationId = table.Column(type: "uuid", nullable: false), - UserId = table.Column(type: "text", nullable: true), - Created = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()"), - Modified = table.Column(type: "timestamp without time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("PK_collaborations", x => x.Id); - table.ForeignKey( - name: "FK_collaborations_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_collaborations_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_collaborations_ApplicationId", - table: "collaborations", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_collaborations_UserId", - table: "collaborations", - column: "UserId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "collaborations"); - - migrationBuilder.AddColumn( - name: "ApplicationId", - table: "AspNetUsers", - type: "uuid", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId"); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.Designer.cs b/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.Designer.cs deleted file mode 100644 index d24db317f..000000000 --- a/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.Designer.cs +++ /dev/null @@ -1,690 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210803220759_CascadingDelete")] - partial class CascadingDelete - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("DomainId") - .HasColumnType("uuid"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.cs b/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.cs deleted file mode 100644 index b3d8fd5dc..000000000 --- a/src/Hippo/Migrations/Postgres/20210803220759_CascadingDelete.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Postgres -{ - public partial class CascadingDelete : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels"); - - migrationBuilder.DropForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables"); - - migrationBuilder.DropForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables", - column: "ConfigurationId", - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels"); - - migrationBuilder.DropForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables"); - - migrationBuilder.DropForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables", - column: "ConfigurationId", - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.Designer.cs b/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.Designer.cs deleted file mode 100644 index fabfe87e5..000000000 --- a/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.Designer.cs +++ /dev/null @@ -1,701 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - [Migration("20210803222847_DomainOwnedByChannel")] - partial class DomainOwnedByChannel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ChannelId") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.HasOne("Hippo.Models.Channel", "Channel") - .WithOne("Domain") - .HasForeignKey("Hippo.Models.Domain", "ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Channel"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Navigation("Domain"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.cs b/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.cs deleted file mode 100644 index 7f3736040..000000000 --- a/src/Hippo/Migrations/Postgres/20210803222847_DomainOwnedByChannel.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Postgres -{ - public partial class DomainOwnedByChannel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Domains_DomainId", - table: "Channels"); - - migrationBuilder.DropIndex( - name: "IX_Channels_DomainId", - table: "Channels"); - - migrationBuilder.DropColumn( - name: "DomainId", - table: "Channels"); - - migrationBuilder.AddColumn( - name: "ChannelId", - table: "Domains", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - - migrationBuilder.CreateIndex( - name: "IX_Domains_ChannelId", - table: "Domains", - column: "ChannelId", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Domains_Channels_ChannelId", - table: "Domains", - column: "ChannelId", - principalTable: "Channels", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Domains_Channels_ChannelId", - table: "Domains"); - - migrationBuilder.DropIndex( - name: "IX_Domains_ChannelId", - table: "Domains"); - - migrationBuilder.DropColumn( - name: "ChannelId", - table: "Domains"); - - migrationBuilder.AddColumn( - name: "DomainId", - table: "Channels", - type: "uuid", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Channels_DomainId", - table: "Channels", - column: "DomainId"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Domains_DomainId", - table: "Channels", - column: "DomainId", - principalTable: "Domains", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Postgres/PostgresDataContextModelSnapshot.cs b/src/Hippo/Migrations/Postgres/PostgresDataContextModelSnapshot.cs deleted file mode 100644 index 482d50a54..000000000 --- a/src/Hippo/Migrations/Postgres/PostgresDataContextModelSnapshot.cs +++ /dev/null @@ -1,699 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Hippo.Migrations.Postgres -{ - [DbContext(typeof(PostgresDataContext))] - partial class PostgresDataContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.7") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("SigningKeyId") - .HasColumnType("uuid"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ActiveRevisionId") - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("PortID") - .HasColumnType("bigint"); - - b.Property("RangeRule") - .HasColumnType("text"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("integer"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ChannelId") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConfigurationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("ChannelId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventKind") - .HasColumnType("integer"); - - b.Property("EventSource") - .HasColumnType("integer"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApplicationId") - .HasColumnType("uuid"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp without time zone") - .HasDefaultValueSql("now()"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.HasOne("Hippo.Models.Channel", "Channel") - .WithOne("Domain") - .HasForeignKey("Hippo.Models.Domain", "ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Channel"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Navigation("Domain"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.Designer.cs b/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.Designer.cs deleted file mode 100644 index 9abe06580..000000000 --- a/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.Designer.cs +++ /dev/null @@ -1,643 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Hippo.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDataContext))] - [Migration("20210712232550_EventHistory")] - partial class EventHistory - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("SigningKeyId") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnType("TEXT"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ActiveRevisionId") - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("DomainId") - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PortID") - .HasColumnType("INTEGER"); - - b.Property("RangeRule") - .HasColumnType("TEXT"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("INTEGER"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborators"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.cs b/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.cs deleted file mode 100644 index ac1c133ec..000000000 --- a/src/Hippo/Migrations/Sqlite/20210712232550_EventHistory.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Sqlite -{ - public partial class EventHistory : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "EventLogEntries", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - EventKind = table.Column(type: "INTEGER", nullable: false), - EventSource = table.Column(type: "INTEGER", nullable: false), - Timestamp = table.Column(type: "TEXT", nullable: false), - ApplicationId = table.Column(type: "TEXT", nullable: true), - ChannelId = table.Column(type: "TEXT", nullable: true), - UserName = table.Column(type: "TEXT", nullable: true), - Description = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") - }, - constraints: table => - { - table.PrimaryKey("PK_EventLogEntries", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "EventLogEntries"); - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.cs b/src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.cs deleted file mode 100644 index 9833195a1..000000000 --- a/src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Sqlite -{ - public partial class Timestamp : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Modified", - table: "Revisions", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Revisions", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Keys", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Keys", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "EventLogEntries", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "EventLogEntries", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "EnvironmentVariables", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "EnvironmentVariables", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Domains", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Domains", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Configuration", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Configuration", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Channels", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Channels", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Applications", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Applications", - type: "TEXT", - nullable: false, - defaultValueSql: "datetime('now')", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "now()"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Modified", - table: "Revisions", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Revisions", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Keys", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Keys", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "EventLogEntries", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "EventLogEntries", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "EnvironmentVariables", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "EnvironmentVariables", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Domains", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Domains", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Configuration", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Configuration", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Channels", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Channels", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Modified", - table: "Applications", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Applications", - type: "TEXT", - nullable: false, - defaultValueSql: "now()", - oldClrType: typeof(DateTime), - oldType: "TEXT", - oldDefaultValueSql: "datetime('now')"); - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.Designer.cs b/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.Designer.cs deleted file mode 100644 index 977afa848..000000000 --- a/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.Designer.cs +++ /dev/null @@ -1,682 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Hippo.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDataContext))] - [Migration("20210726214403_Collaborations")] - partial class Collaborations - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("SigningKeyId") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnType("TEXT"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ActiveRevisionId") - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("DomainId") - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PortID") - .HasColumnType("INTEGER"); - - b.Property("RangeRule") - .HasColumnType("TEXT"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("INTEGER"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId"); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.cs b/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.cs deleted file mode 100644 index f343482da..000000000 --- a/src/Hippo/Migrations/Sqlite/20210726214403_Collaborations.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Sqlite -{ - public partial class Collaborations : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.DropIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.DropColumn( - name: "ApplicationId", - table: "AspNetUsers"); - - migrationBuilder.CreateTable( - name: "collaborations", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - ApplicationId = table.Column(type: "TEXT", nullable: false), - UserId = table.Column(type: "TEXT", nullable: true), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") - }, - constraints: table => - { - table.PrimaryKey("PK_collaborations", x => x.Id); - table.ForeignKey( - name: "FK_collaborations_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_collaborations_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_collaborations_ApplicationId", - table: "collaborations", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_collaborations_UserId", - table: "collaborations", - column: "UserId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "collaborations"); - - migrationBuilder.AddColumn( - name: "ApplicationId", - table: "AspNetUsers", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId"); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.Designer.cs b/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.Designer.cs deleted file mode 100644 index 3d63b6d3c..000000000 --- a/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.Designer.cs +++ /dev/null @@ -1,685 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Hippo.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDataContext))] - [Migration("20210803220823_CascadingDelete")] - partial class CascadingDelete - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("SigningKeyId") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnType("TEXT"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ActiveRevisionId") - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("DomainId") - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PortID") - .HasColumnType("INTEGER"); - - b.Property("RangeRule") - .HasColumnType("TEXT"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("INTEGER"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("Domain"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.cs b/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.cs deleted file mode 100644 index 339fa41c3..000000000 --- a/src/Hippo/Migrations/Sqlite/20210803220823_CascadingDelete.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Sqlite -{ - public partial class CascadingDelete : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels"); - - migrationBuilder.DropForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables"); - - migrationBuilder.DropForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables", - column: "ConfigurationId", - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels"); - - migrationBuilder.DropForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables"); - - migrationBuilder.DropForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Applications_ApplicationId", - table: "Channels", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - table: "EnvironmentVariables", - column: "ConfigurationId", - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - table: "Revisions", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.Designer.cs b/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.Designer.cs deleted file mode 100644 index 052d13d09..000000000 --- a/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.Designer.cs +++ /dev/null @@ -1,696 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Hippo.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDataContext))] - [Migration("20210803222521_DomainOwnedByChannel")] - partial class DomainOwnedByChannel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("SigningKeyId") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnType("TEXT"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ActiveRevisionId") - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PortID") - .HasColumnType("INTEGER"); - - b.Property("RangeRule") - .HasColumnType("TEXT"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("INTEGER"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ChannelId") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.HasOne("Hippo.Models.Channel", "Channel") - .WithOne("Domain") - .HasForeignKey("Hippo.Models.Domain", "ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Channel"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Navigation("Domain"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.cs b/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.cs deleted file mode 100644 index 30b44cc4e..000000000 --- a/src/Hippo/Migrations/Sqlite/20210803222521_DomainOwnedByChannel.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Hippo.Migrations.Sqlite -{ - public partial class DomainOwnedByChannel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Channels_Domains_DomainId", - table: "Channels"); - - migrationBuilder.DropIndex( - name: "IX_Channels_DomainId", - table: "Channels"); - - migrationBuilder.DropColumn( - name: "DomainId", - table: "Channels"); - - migrationBuilder.AddColumn( - name: "ChannelId", - table: "Domains", - type: "TEXT", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - - migrationBuilder.CreateIndex( - name: "IX_Domains_ChannelId", - table: "Domains", - column: "ChannelId", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Domains_Channels_ChannelId", - table: "Domains", - column: "ChannelId", - principalTable: "Channels", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Domains_Channels_ChannelId", - table: "Domains"); - - migrationBuilder.DropIndex( - name: "IX_Domains_ChannelId", - table: "Domains"); - - migrationBuilder.DropColumn( - name: "ChannelId", - table: "Domains"); - - migrationBuilder.AddColumn( - name: "DomainId", - table: "Channels", - type: "TEXT", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Channels_DomainId", - table: "Channels", - column: "DomainId"); - - migrationBuilder.AddForeignKey( - name: "FK_Channels_Domains_DomainId", - table: "Channels", - column: "DomainId", - principalTable: "Domains", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Hippo/Migrations/Sqlite/SqliteDataContextModelSnapshot.cs b/src/Hippo/Migrations/Sqlite/SqliteDataContextModelSnapshot.cs deleted file mode 100644 index 5ae141617..000000000 --- a/src/Hippo/Migrations/Sqlite/SqliteDataContextModelSnapshot.cs +++ /dev/null @@ -1,694 +0,0 @@ -// -using System; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Hippo.Migrations.Sqlite -{ - [DbContext(typeof(SqliteDataContext))] - partial class SqliteDataContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("SigningKeyId") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") - .HasColumnType("TEXT"); - - b.Property("StorageId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ActiveRevisionId") - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PortID") - .HasColumnType("INTEGER"); - - b.Property("RangeRule") - .HasColumnType("TEXT"); - - b.Property("RevisionSelectionStrategy") - .HasColumnType("INTEGER"); - - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ActiveRevisionId"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("SpecifiedRevisionId"); - - b.ToTable("Channels"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("UserId"); - - b.ToTable("collaborations"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); - - b.ToTable("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ChannelId") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Domains"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConfigurationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ConfigurationId"); - - b.ToTable("EnvironmentVariables"); - }); - - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Timestamp") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("EventLogEntries"); - }); - - modelBuilder.Entity("Hippo.Models.Key", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("PrivateKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Keys"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("RevisionNumber") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.ToTable("Revisions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") - .WithMany() - .HasForeignKey("ActiveRevisionId"); - - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Channels") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); - - b.Navigation("ActiveRevision"); - - b.Navigation("Application"); - - b.Navigation("Configuration"); - - b.Navigation("SpecifiedRevision"); - }); - - modelBuilder.Entity("Hippo.Models.Collaboration", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Collaborations") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.HasOne("Hippo.Models.Channel", "Channel") - .WithOne("Domain") - .HasForeignKey("Hippo.Models.Domain", "ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Channel"); - }); - - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => - { - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Configuration"); - }); - - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.HasOne("Hippo.Models.Application", "Application") - .WithMany("Revisions") - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Application"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hippo.Models.Account", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Navigation("Channels"); - - b.Navigation("Collaborations"); - - b.Navigation("Revisions"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.Navigation("Domain"); - }); - - modelBuilder.Entity("Hippo.Models.Configuration", b => - { - b.Navigation("EnvironmentVariables"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Hippo/Models/Account.cs b/src/Hippo/Models/Account.cs deleted file mode 100644 index fc10b6738..000000000 --- a/src/Hippo/Models/Account.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Hippo.Models; - -/// -/// The generic IdentityUser has a lot of built in properties -/// that can be used for identities. -/// By using IdentityUser to inherit we are saying that we want to add on to it. -/// -public class Account : IdentityUser -{ - /// - /// Personal signing key used for signing off releases and determining trust. - /// - public virtual Key SigningKey { get; set; } -} diff --git a/src/Hippo/Models/Application.cs b/src/Hippo/Models/Application.cs deleted file mode 100644 index 1b20b5fd0..000000000 --- a/src/Hippo/Models/Application.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace Hippo.Models; - -public class Application : BaseEntity -{ - - [Required] - public string Name { get; set; } - - // This is the ID in Bindle or whatever storage backend is used. It gets composed - // with a revision ID to get a Bindle ID. - // - // For example, the Weather application might have the StorageId contoso/weather. - // Revision 1.4.0 of the Weather application would then have the bindle id - // contoso/weather/1.4.0 - [Required] - public string StorageId { get; set; } - - [Required] - public virtual Account Owner { get; set; } - - [Required] - public virtual ICollection Collaborations { get; set; } - - [Required] - public virtual ICollection Revisions { get; set; } - - [Required] - public virtual ICollection Channels { get; set; } - - public IReadOnlyList ReevaluateActiveRevisions() - { - return ReevaluateActiveRevisionsLazy().ToList().AsReadOnly(); - } - - public IEnumerable ReevaluateActiveRevisionsLazy() - { - if (Channels == null) - { - yield break; - } - foreach (var channel in Channels) - { - var change = channel.ReevaluateActiveRevision(); - if (change != null) - { - yield return change; - } - } - // TODO: should this trigger a redeploy? - } - - public HealthStatus Status() - { - if (Channels != null) - { - var unhealthyChannels = Channels.Select(c => new { Name = c.Name, Status = c.Status() }) - .Where(c => c.Status.Health == HealthLevel.Unhealthy) - .ToList(); - if (unhealthyChannels.Any()) - { - var message = $"{unhealthyChannels.Count} channel(s) unhealthy: {string.Join(", ", unhealthyChannels.Select(c => c.Name))}"; - return HealthStatus.Unhealthy(message); - } - } - return HealthStatus.Healthy; - } - - public ICollection SafeCollaborations() => - Collaborations ?? Array.Empty(); -} diff --git a/src/Hippo/Models/BaseEntity.cs b/src/Hippo/Models/BaseEntity.cs deleted file mode 100644 index cbcd6df70..000000000 --- a/src/Hippo/Models/BaseEntity.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Hippo.Models; - -public abstract class BaseEntity -{ - protected BaseEntity() - { - Created = DateTime.Now; - Modified = DateTime.Now; - } - - [Key] - public Guid Id { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public DateTime Created { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Computed)] - public DateTime Modified { get; set; } -} diff --git a/src/Hippo/Models/Channel.cs b/src/Hippo/Models/Channel.cs deleted file mode 100644 index 55ab52744..000000000 --- a/src/Hippo/Models/Channel.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -using Hippo.Rules; - -namespace Hippo.Models; - -public class Channel : BaseEntity -{ - public const int EphemeralPortRange = 32768; - - public string Name { get; set; } - - public ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; set; } - - public Revision SpecifiedRevision { get; set; } - public string RangeRule { get; set; } - - public Revision ActiveRevision { get; set; } - - public Application Application { get; set; } - public Domain Domain { get; set; } - - // TODO: doesn't work in memory or with SQLite - // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public uint PortID { get; set; } - public Configuration Configuration { get; set; } - - public ActiveRevisionChange ReevaluateActiveRevision() - { - var previous = ActiveRevision; - - switch (RevisionSelectionStrategy) - { - case ChannelRevisionSelectionStrategy.UseSpecifiedRevision: - ActiveRevision = SpecifiedRevision; - break; - case ChannelRevisionSelectionStrategy.UseRangeRule: - ActiveRevision = RevisionRangeRule.Parse(RangeRule).Match(Application.Revisions); - break; - default: - throw new InvalidOperationException($"Unknown revision strategy {RevisionSelectionStrategy}"); - - } - // TODO: should this trigger a redeploy? - // TODO: if we end up with no active revision then we should put the channel into - // some kind of unhappy status - - if (ActiveRevision?.RevisionNumber == previous?.RevisionNumber) - { - return null; - } - - return new ActiveRevisionChange(previous?.RevisionNumber, ActiveRevision?.RevisionNumber, this); - } - - public HealthStatus Status() - { - if (ActiveRevision == null) - { - return HealthStatus.Unhealthy("No active revision"); - } - return HealthStatus.Healthy; - } - - // TODO: this will change to the domain when we get the reverse proxy working - public string ServedOn() - { - return (PortID + EphemeralPortRange).ToString(CultureInfo.InvariantCulture) + (Domain == null ? "" : $" or {Domain.Name}"); - } - - public ICollection GetEnvironmentVariables() => - Configuration?.EnvironmentVariables ?? Array.Empty(); - - public string ConfigurationSummary() - { - var strategy = RevisionSelectionStrategy switch - { - ChannelRevisionSelectionStrategy.UseSpecifiedRevision => - $"fixed revision {SpecifiedRevision?.RevisionNumber ?? "(none)"}", - ChannelRevisionSelectionStrategy.UseRangeRule => - $"rule {RangeRule}", - _ => "invalid", - }; - var domain = Domain?.Name ?? "(none)"; - - var sb = new StringBuilder(); - sb.AppendFormat(CultureInfo.InvariantCulture, $"strategy: {strategy}; domain: {domain}"); - return sb.ToString(); - } -} - -/// -/// The strategy to use to select a revision for the Channel -/// -public enum ChannelRevisionSelectionStrategy -{ - // IMPORTANT: The underlying values here are contractual with the database. - // **DO NOT** change the underlying numeric value of any case. - /// - /// Use a range rule to select the most appropriate revision for the channel - /// - UseRangeRule = 0, - /// - /// Use a specific revision version for the channel - /// - UseSpecifiedRevision = 1, -} - -public record ActiveRevisionChange( - string ChangedFrom, - string ChangedTo, - Channel Channel - ); diff --git a/src/Hippo/Models/Collaboration.cs b/src/Hippo/Models/Collaboration.cs deleted file mode 100644 index d7a454bbc..000000000 --- a/src/Hippo/Models/Collaboration.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class Collaboration : BaseEntity -{ - [Required] - public Application Application { get; set; } - [Required] - public Account User { get; set; } -} diff --git a/src/Hippo/Models/Configuration.cs b/src/Hippo/Models/Configuration.cs deleted file mode 100644 index 6060b7e56..000000000 --- a/src/Hippo/Models/Configuration.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class Configuration : BaseEntity -{ - [Required] - public virtual ICollection EnvironmentVariables { get; set; } -} diff --git a/src/Hippo/Models/DataContext.cs b/src/Hippo/Models/DataContext.cs deleted file mode 100644 index 3b9957524..000000000 --- a/src/Hippo/Models/DataContext.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; - -namespace Hippo.Models; - -public abstract class DataContext : IdentityDbContext -{ - private protected readonly IConfiguration _configuration; - - protected DataContext(IConfiguration configuration) - { - _configuration = configuration; - } - - public DbSet Accounts { get; set; } - public DbSet Applications { get; set; } - public DbSet Channels { get; set; } - public DbSet collaborations { get; set; } - public DbSet Configuration { get; set; } - public DbSet Domains { get; set; } - public DbSet EnvironmentVariables { get; set; } - public DbSet EventLogEntries { get; set; } - public DbSet Keys { get; set; } - public DbSet Revisions { get; set; } - - private protected abstract string SqlNow { get; } - - private IEnumerable BaseEntitySetTypes() - { - IEnumerable BaseEntitySetTypesImpl(PropertyInfo p) - { - var type = p.PropertyType; - if (type.IsGenericType && - type.GetGenericTypeDefinition() == typeof(DbSet<>) && - type.GetGenericArguments().Length == 1) - { - var entityType = type.GetGenericArguments()[0]; - if (entityType.IsAssignableTo(typeof(BaseEntity))) - { - yield return entityType; - } - } - } - return GetType().GetProperties().SelectMany(BaseEntitySetTypesImpl); - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - foreach (var t in BaseEntitySetTypes()) - { - builder.Entity(t).Property(nameof(BaseEntity.Created)).HasDefaultValueSql(SqlNow); - builder.Entity(t).Property(nameof(BaseEntity.Modified)).HasDefaultValueSql(SqlNow); - } - - builder.Entity() - .HasIndex(a => a.Name) - .IsUnique(); - - builder.Entity() - .HasMany(a => a.Channels) - .WithOne(c => c.Application) - .OnDelete(DeleteBehavior.Cascade); - - builder.Entity() - .HasMany(a => a.Revisions) - .WithOne(r => r.Application) - .OnDelete(DeleteBehavior.Cascade); - - - builder.Entity() - .HasMany(c => c.EnvironmentVariables) - .WithOne(e => e.Configuration) - .OnDelete(DeleteBehavior.Cascade); - - builder.Entity() - .HasIndex(d => d.Name) - .IsUnique(); - - builder.Entity() - .Property(c => c.RevisionSelectionStrategy) - .HasConversion(); - - builder.Entity() - .HasOne(c => c.Domain) - .WithOne(d => d.Channel) - .HasForeignKey(d => d.ChannelId) - .OnDelete(DeleteBehavior.Cascade); - - builder.Entity() - .Property(c => c.EventKind) - .HasConversion(); - - builder.Entity() - .Property(c => c.EventSource) - .HasConversion(); - } -} - -public class PostgresDataContext : DataContext -{ - public PostgresDataContext(IConfiguration configuration) : base(configuration) - { - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql(_configuration.GetConnectionString("Database")); - } - - private protected override string SqlNow => "now()"; -} - -public class SqliteDataContext : DataContext -{ - public SqliteDataContext(IConfiguration configuration) : base(configuration) - { - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlite(_configuration.GetConnectionString("Database")); - } - - private protected override string SqlNow => "datetime('now')"; -} - -// Convenient for dev-test when the feature requires exploratory changes to -// the database schema -public class InMemoryDataContext : DataContext -{ - private readonly string _databaseName; - - public InMemoryDataContext(string databaseName = "Hippo") : base(null) - { - _databaseName = databaseName; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseInMemoryDatabase(_databaseName); - } - } - - private protected override string SqlNow => "now()"; -} diff --git a/src/Hippo/Models/Domain.cs b/src/Hippo/Models/Domain.cs deleted file mode 100644 index 09d5ebace..000000000 --- a/src/Hippo/Models/Domain.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class Domain : BaseEntity -{ - [Required] - public string Name { get; set; } - - public Guid ChannelId { get; set; } - public Channel Channel { get; set; } -} diff --git a/src/Hippo/Models/EnvironmentVariable.cs b/src/Hippo/Models/EnvironmentVariable.cs deleted file mode 100644 index 32a7503f2..000000000 --- a/src/Hippo/Models/EnvironmentVariable.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class EnvironmentVariable : BaseEntity -{ - public Configuration Configuration { get; set; } - - [Required] - public string Key { get; set; } - - [Required] - public string Value { get; set; } -} diff --git a/src/Hippo/Models/EventLogEntry.cs b/src/Hippo/Models/EventLogEntry.cs deleted file mode 100644 index 5659e42e9..000000000 --- a/src/Hippo/Models/EventLogEntry.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class EventLogEntry : BaseEntity -{ - [Required] - public EventKind EventKind { get; set; } - - [Required] - public EventOrigin EventSource { get; set; } - - [Required] - public DateTime Timestamp { get; set; } - - // Use IDs rather than object references so we don't end up with FK constraints - // that result in logs for old apps being inadvertently deleted - - public Guid? ApplicationId { get; set; } - - public Guid? ChannelId { get; set; } - - public string UserName { get; set; } - - [Required] - public string Description { get; set; } -} - -public enum EventKind -{ - // IMPORTANT: The underlying values here are contractual with the database. - // **DO NOT** change the underlying numeric value of any case. - // - // No! Not even if it throws off the increasing sequence, or the alphabetical - // order, or whatever you feel is aesthetically important. The values are - // a CONTRACT. If you change them YOU WILL BREAK EVENT LOGGING. - - AccountCreated = 0, - AccountLogin = 1, - AccountLoginFailed = 2, - ApplicationCreated = 3, - ApplicationEdited = 4, - ChannelCreated = 5, - ChannelEdited = 6, - ChannelRevisionChanged = 7, - RevisionRegistered = 8, - ChannelDeleted = 9, -} - -public enum EventOrigin -{ - // IMPORTANT: The underlying values here are contractual with the database. - // **DO NOT** change the underlying numeric value of any case. - // - // Go read the warning on EventKind and just don't mess with the values. - - UI = 0, - API = 1, -} diff --git a/src/Hippo/Models/HealthStatus.cs b/src/Hippo/Models/HealthStatus.cs deleted file mode 100644 index 859b531ee..000000000 --- a/src/Hippo/Models/HealthStatus.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Hippo.Models; - -public enum HealthLevel -{ - Healthy, - Unhealthy, -} - -public class HealthStatus -{ - public HealthLevel Health { get; } - public IReadOnlyCollection Messages { get; } - - public static readonly HealthStatus Healthy = new HealthStatus(HealthLevel.Healthy); - - public static HealthStatus Unhealthy(string message) => - new HealthStatus(HealthLevel.Unhealthy, new[] { message }); - - public HealthStatus(HealthLevel health) - : this(health, Enumerable.Empty()) { } - - public HealthStatus(HealthLevel health, IEnumerable messages) - { - Health = health; - Messages = new List(messages).AsReadOnly(); - } -} diff --git a/src/Hippo/Models/Key.cs b/src/Hippo/Models/Key.cs deleted file mode 100644 index d32c32f06..000000000 --- a/src/Hippo/Models/Key.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hippo.Models; - -public class Key : BaseEntity -{ - /// - /// public key used for determining trust. - /// - [Required] - public string PublicKey { get; set; } - - /// - /// Private key used for signing releases. - /// - [Required] - public string PrivateKey { get; set; } -} diff --git a/src/Hippo/Models/Revision.cs b/src/Hippo/Models/Revision.cs deleted file mode 100644 index b7d3cf822..000000000 --- a/src/Hippo/Models/Revision.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace Hippo.Models; - -public class Revision : BaseEntity -{ - public virtual Application Application { get; set; } - - // This is the revision number that gets composed with the Application.StorageId - // to get the bindle ID. E.g. this might be "1.4.0" or "1.1.5-prerelease2". - [Required] - public string RevisionNumber { get; set; } - - public IEnumerable ActiveOn() => - Application.Channels.Where(c => c.ActiveRevision?.RevisionNumber == RevisionNumber); - - public string OrderKey() - { - if (SemVer.Version.TryParse(RevisionNumber, out var version)) - { - return $"{version.Major:D9}{version.Minor:D9}{version.Patch:D9}{RevisionNumber}"; - } - return RevisionNumber; - } -} diff --git a/src/Hippo/OperationData/ICreateApplicationParameters.cs b/src/Hippo/OperationData/ICreateApplicationParameters.cs deleted file mode 100644 index be8c3d20e..000000000 --- a/src/Hippo/OperationData/ICreateApplicationParameters.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Hippo.OperationData; - -public interface ICreateApplicationParameters -{ - string ApplicationName { get; } - string StorageId { get; } -} diff --git a/src/Hippo/OperationData/ICreateChannelParameters.cs b/src/Hippo/OperationData/ICreateChannelParameters.cs deleted file mode 100644 index 9e7f09323..000000000 --- a/src/Hippo/OperationData/ICreateChannelParameters.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using Hippo.Models; - -namespace Hippo.OperationData; - -public interface ICreateChannelParameters -{ - Guid ApplicationId { get; } - string ChannelName { get; } - string DomainName { get; } - Dictionary EnvironmentVariables { get; } - ChannelRevisionSelectionStrategy RevisionSelectionStrategy { get; } - string RevisionNumber { get; } - string RangeRule { get; } -} diff --git a/src/Hippo/Program.cs b/src/Hippo/Program.cs deleted file mode 100644 index 7bb82d72e..000000000 --- a/src/Hippo/Program.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Text; -using System.Text.Json.Serialization; -using Hippo.Config; -using Hippo.Models; -using Hippo.Proxies; -using Hippo.Repositories; -using Hippo.Schedulers; -using Hippo.Tasks; -using Hippo.WagiDotnet; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; - -static WebApplicationBuilder CreateHippoWebApplicationBuilder(string[] args, ChannelConfigurationProvider channelConfigurationProvider) -{ - var builder = WebApplication.CreateBuilder(args); - - builder.Services.AddRouting(options => options.LowercaseUrls = true); - - builder.Services.AddMvc().AddJsonOptions(options => - options.JsonSerializerOptions.Converters.Add(new - JsonStringEnumConverter())); - - // authentication/authorization - builder.Services.AddIdentity(cfg => - { - cfg.User.RequireUniqueEmail = true; - }).AddEntityFrameworkStores(); - - builder.Services.AddAuthentication().AddCookie().AddJwtBearer(cfg => - cfg.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = builder.Configuration["Jwt:Issuer"], - ValidAudience = builder.Configuration["Jwt:Audience"], - IssuerSigningKey = new - SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) - }); - - builder.Services.AddAuthorization(options => - { - options.AddPolicy("RequireAdministratorRole", - policy => policy.RequireRole("Administrator")); - }); - - // data context - var driver = builder.Configuration.GetValue("Database:Driver", "inmemory").ToLower(); - switch (driver) - { - case "inmemory": - builder.Services.AddDbContext(); - break; - case "postgresql": - builder.Services.AddDbContext(); - break; - case "sqlite": - builder.Services.AddDbContext(); - break; - default: - throw new ArgumentException(String.Format("{0} is not a valid database driver", driver)); - } - - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddSingleton, TaskQueue>(); - builder.Services.AddSingleton, TaskQueue>(); - builder.Services.AddSingleton(); - - // job scheduler - builder.Services.AddHostedService(); - var schedulerDriver = builder.Configuration.GetValue("Scheduler:Driver", "wagi").ToLower(); - switch (schedulerDriver) - { - case "wagi-dotnet": - builder.Services.AddSingleton(); - break; - case "wagi": - builder.Services.AddSingleton(); - break; - default: - throw new ArgumentException(String.Format("{0} is not a valid scheduler driver", schedulerDriver)); - } - - builder.WebHost.UseKestrel(options => - { - options.ListenAnyIP(builder.Configuration.GetValue("Kestrel:Endpoints:Http:Port", 5308)); - options.ListenAnyIP( - builder.Configuration.GetValue("Kestrel:Endpoints:Https:Port", 5309), - listenOptions => - { - listenOptions.UseHttps(); - }); - } - ); - - builder.Services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "hippo API", - Version = "v1" - }); - c.AddSecurityDefinition("http", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.Http, - BearerFormat = "JWT", - Scheme = "Bearer" - }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement { { - new OpenApiSecurityScheme { - Reference = new OpenApiReference { - Type = ReferenceType.SecurityScheme, - Id = "http" - } - }, - Array.Empty() } }); - }); - - return builder; -} - -static IHostBuilder CreateProxyHostBuilder(ITaskQueue proxyUpdateTaskQueue) -{ - var builder = Host.CreateDefaultBuilder() - .UseConsoleLifetime() - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "Proxies")) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.Sources.Clear(); - config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables("HIPPO_REVERSE_PROXY_"); - }) - .ConfigureServices(services => - { - services.AddSingleton(proxyUpdateTaskQueue); - services.AddHostedService(); - }); - return builder; -} - -static IHostBuilder CreateWagiDotnetHostBuilder(ChannelConfigurationProvider channelConfigurationProvider) -{ - var builder = Host.CreateDefaultBuilder() - .UseConsoleLifetime() - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "WagiDotnet")) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.Sources.Clear(); - config.AddChannelConfiguration(channelConfigurationProvider) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables("WAGI_DOTNET_"); - - }) - .ConfigureServices(services => - { - services.AddSingleton(channelConfigurationProvider); - }); - return builder; -} - -var tasks = new List(); -var builder = CreateHippoWebApplicationBuilder(args, null); - -// The WAGI.NET and Hippo hosts share some services -if (builder.Configuration.GetValue("Scheduler:Driver", "wagi").ToLower() == "wagi-dotnet") -{ - var channelConfigProvider = new ChannelConfigurationProvider(); - var wagiDotnetHost = CreateWagiDotnetHostBuilder(channelConfigProvider).Build(); - tasks.Add(wagiDotnetHost.RunAsync()); - builder.Services.AddSingleton(channelConfigProvider); -} - -var hippoHost = builder.Build(); - -if (builder.Configuration.GetValue("ProxyEnabled", false)) -{ - var proxyUpdateTaskQueue = hippoHost.Services.GetRequiredService>(); - var proxyHostBuilder = CreateProxyHostBuilder(proxyUpdateTaskQueue); - var proxyHost = proxyHostBuilder.Build(); - tasks.Add(proxyHost.RunAsync()); -} - -// Configure the HTTP request pipeline. -if (hippoHost.Environment.IsDevelopment()) -{ - hippoHost.UseDeveloperExceptionPage(); - // run database migrations - using var scope = hippoHost.Services.CreateScope(); - var dataContext = scope.ServiceProvider.GetService(); - dataContext.Database.Migrate(); -} - -hippoHost.UseSwagger(); -hippoHost.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "hippo v1")); -hippoHost.UseHttpsRedirection(); -hippoHost.UseStaticFiles(); -hippoHost.UseRouting(); -hippoHost.UseAuthentication(); -hippoHost.UseAuthorization(); -hippoHost.MapControllerRoute( - name: "default", - pattern: "{controller=App}/{action=Index}/{id?}" -); - -tasks.Add(hippoHost.RunAsync()); - -Task.WaitAny(tasks.ToArray()); diff --git a/src/Hippo/Proxies/ChannelConfigProvider.cs b/src/Hippo/Proxies/ChannelConfigProvider.cs deleted file mode 100644 index 807f882c2..000000000 --- a/src/Hippo/Proxies/ChannelConfigProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Hippo.Config; -using Hippo.Tasks; -using Microsoft.Extensions.Logging; -using Yarp.ReverseProxy.Configuration; - -namespace Hippo.Proxies; - -public class ChannelConfigProvider : IProxyConfigProvider, IReverseProxyUpdater -{ - private volatile ChannelConfig _config; - private readonly ILogger _logger; - private readonly Dictionary _routes; - private readonly Dictionary _clusters; - - public ChannelConfigProvider(ILogger logger) - { - _routes = new Dictionary(); - _clusters = new Dictionary(); - _config = new ChannelConfig(); - _logger = logger; - } - - public IProxyConfig GetConfig() => _config; - - public void UpdateConfig() - { - _logger.LogTrace($"Updating YARP Config"); - var existingConfig = _config; - var routes = _routes.Values.ToList(); - var clusters = _clusters.Values.ToList(); - var newConfig = new ChannelConfig(routes, clusters); - _config = newConfig; - - // Need to signal to YARP to get the new configuration by calling SignalChange on the existingConfiguration. - existingConfig.SignalChange(); - } - - public bool UpdateProxyRecord(ReverseProxyUpdateRequest record) - { - if (record.Action == ReverseProxyAction.Stop) - { - return DeleteProxyRecord(record); - } - - if (record.Action == ReverseProxyAction.Start) - { - AddOrUpdateProxyRecord(record); - return true; - } - - return false; - } - private void AddOrUpdateProxyRecord(ReverseProxyUpdateRequest record) - { - _logger.LogTrace($"Processing Proxy Start Request for Application: {record.ApplicationId} Channel: {record.ChannelId} Host: {record.Domain} Address: {record.Host}"); - var key = GetKey(record.ApplicationId, record.ChannelId); - var clusterConfig = new ClusterConfig() - { - ClusterId = key, - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { - key, new DestinationConfig() - { - Address = record.Host - } - } - } - }; - - _clusters[key] = clusterConfig; - - var routeConfig = new RouteConfig() - { - RouteId = key, - ClusterId = key, - Match = new RouteMatch - { - Hosts = new List() - { - record.Domain - } - } - }; - - _routes[key] = routeConfig; - } - - private bool DeleteProxyRecord(ReverseProxyUpdateRequest record) - { - _logger.LogTrace($"Processing Proxy Stop Request for Application: {record.ApplicationId} Channel: {record.ChannelId} Host: {record.Domain}"); - var key = GetKey(record.ApplicationId, record.ChannelId); - var removedRoute = _routes.Remove(key); - if (!removedRoute) - { - _logger.LogError($"Attempted to remove route for key:{key} but it did not exist in Dictionary"); - } - - var removedCluster = _clusters.Remove(key); - if (!removedCluster) - { - _logger.LogError($"Attempted to remove cluster for key:{key} but it did not exist in Dictionary"); - } - - return removedCluster || removedRoute; - } - - private static string GetKey(Guid appId, Guid channelId) => $"App:{appId}-Channel:{channelId}"; -} diff --git a/src/Hippo/Proxies/IReverseProxy.cs b/src/Hippo/Proxies/IReverseProxy.cs deleted file mode 100644 index 07bbac65e..000000000 --- a/src/Hippo/Proxies/IReverseProxy.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Hippo.Models; - -namespace Hippo.Proxies; - -public interface IReverseProxy -{ - void StartProxy(Channel channel, string address); - void StopProxy(Channel channel); -} diff --git a/src/Hippo/Proxies/IReverseProxyUpdater.cs b/src/Hippo/Proxies/IReverseProxyUpdater.cs deleted file mode 100644 index 41aa885c3..000000000 --- a/src/Hippo/Proxies/IReverseProxyUpdater.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Hippo.Tasks; - -namespace Hippo.Proxies; - -public interface IReverseProxyUpdater -{ - bool UpdateProxyRecord(ReverseProxyUpdateRequest record); - void UpdateConfig(); -} diff --git a/src/Hippo/Proxies/ProxyStartup.cs b/src/Hippo/Proxies/ProxyStartup.cs deleted file mode 100644 index 2396a2166..000000000 --- a/src/Hippo/Proxies/ProxyStartup.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Hippo.Extensions; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Hippo.Proxies; - -public class ProxyStartup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddReverseProxy() - .LoadFromHippoChannels(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - // TODO: Configure an exception handler. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapReverseProxy(); - }); - } -} diff --git a/src/Hippo/Proxies/YarpReverseProxy.cs b/src/Hippo/Proxies/YarpReverseProxy.cs deleted file mode 100644 index cbba35552..000000000 --- a/src/Hippo/Proxies/YarpReverseProxy.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading; -using Hippo.Models; -using Hippo.Schedulers; -using Hippo.Tasks; - -namespace Hippo.Proxies; - -public class YarpReverseProxy : IReverseProxy -{ - private readonly ITaskQueue _reverseProxyConfigQueue; - public YarpReverseProxy(ITaskQueue reverseProxyConfigQueue, JobScheduler scheduler) - { - _reverseProxyConfigQueue = reverseProxyConfigQueue; - scheduler.ChannelStarted += new EventHandler((o, e) => StartProxy(e.Channel, e.ListenAddress)); - scheduler.ChannelStopped += new EventHandler((o, e) => StopProxy(e)); - } - - public void StopProxy(Channel channel) - { - _reverseProxyConfigQueue.Enqueue(new ReverseProxyUpdateRequest(channel.Application.Id, channel.Id, null, channel.Domain.Name, ReverseProxyAction.Stop), CancellationToken.None).Wait(); - } - - public void StartProxy(Channel channel, string address) - { - _reverseProxyConfigQueue.Enqueue(new ReverseProxyUpdateRequest(channel.Application.Id, channel.Id, address, channel.Domain.Name, ReverseProxyAction.Start), CancellationToken.None).Wait(); - } -} diff --git a/src/Hippo/Proxies/appsettings.Development.json b/src/Hippo/Proxies/appsettings.Development.json deleted file mode 100644 index 15ae47006..000000000 --- a/src/Hippo/Proxies/appsettings.Development.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Hippo": "Trace", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://0.0.0.0:5310" - }, - "Https": { - "Url": "https://0.0.0.0:5311" - } - } - } -} diff --git a/src/Hippo/Proxies/appsettings.json b/src/Hippo/Proxies/appsettings.json deleted file mode 100644 index 7c3eba2e7..000000000 --- a/src/Hippo/Proxies/appsettings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://0.0.0.0:80" - }, - "Https": { - "Url": "https://0.0.0.0:443" - } - } - } -} diff --git a/src/Hippo/Repositories/ActionContextCurrentUser.cs b/src/Hippo/Repositories/ActionContextCurrentUser.cs deleted file mode 100644 index 65a84a8c3..000000000 --- a/src/Hippo/Repositories/ActionContextCurrentUser.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Infrastructure; - -namespace Hippo.Repositories; - -public class ActionContextCurrentUser : ICurrentUser -{ - private readonly IActionContextAccessor _actionContext; - - public ActionContextCurrentUser(IActionContextAccessor actionContext) - { - _actionContext = actionContext; - } - - public string Name() => _actionContext.ActionContext.HttpContext.User.Identity.Name; -} diff --git a/src/Hippo/Repositories/DbAccountRepository.cs b/src/Hippo/Repositories/DbAccountRepository.cs deleted file mode 100644 index f53ac19c0..000000000 --- a/src/Hippo/Repositories/DbAccountRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Linq; -using Hippo.Models; - -namespace Hippo.Repositories; - -public class DbAccountRepository : IAccountRepository -{ - private readonly DataContext _context; - - public DbAccountRepository(DataContext context) - { - _context = context; - } - - public bool IsEmpty() => - !_context.Accounts.Any(); -} diff --git a/src/Hippo/Repositories/DbApplicationRepository.cs b/src/Hippo/Repositories/DbApplicationRepository.cs deleted file mode 100644 index 7981e5d98..000000000 --- a/src/Hippo/Repositories/DbApplicationRepository.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; - -namespace Hippo.Repositories; - -public class DbApplicationRepository : IApplicationRepository -{ - private readonly DataContext _context; - private readonly ICurrentUser _owner; - - public DbApplicationRepository(DataContext context, ICurrentUser owner) - { - _context = context; - _owner = owner; - } - - public IEnumerable ListApplications() => - _context.Applications - .Where(application => application.Owner.UserName == _owner.Name() || application.Collaborations.Any(c => c.User.UserName == _owner.Name())) - .Include(a => a.Channels) - .ThenInclude(c => c.Domain) - .Include(a => a.Collaborations) - .ThenInclude(c => c.User) - .Include(a => a.Revisions); - - public IEnumerable ListApplicationsForAllUsers() => - _context.Applications - .Include(a => a.Channels) - .ThenInclude(c => c.Domain) - .Include(a => a.Collaborations) - .ThenInclude(c => c.User) - .Include(a => a.Revisions); - - public IEnumerable ListApplicationsByStorageId(string storageId) => - _context.Applications - .Where(application => application.StorageId == storageId && (application.Owner.UserName == _owner.Name() || application.Collaborations.Any(c => c.User.UserName == _owner.Name()))) - .Include(a => a.Channels) - .ThenInclude(c => c.Domain) - .Include(a => a.Collaborations) - .ThenInclude(c => c.User) - .Include(a => a.Revisions); - - public Application GetApplicationById(Guid id) => - _context.Applications - .Where(application => application.Id == id && (application.Owner.UserName == _owner.Name() || application.Collaborations.Any(c => c.User.UserName == _owner.Name()))) - .Include(a => a.Channels) - .ThenInclude(c => c.Domain) - .Include(a => a.Collaborations) - .ThenInclude(c => c.User) - .Include(a => a.Revisions) - .SingleOrDefault(); - - public bool ApplicationExistsById(Guid id) => - _context.Applications.Find(id) != null; - - public async Task AddNew(Application application) - { - await _context.Applications.AddAsync(application); - } - - public void Update(Application application) - { - _context.Applications.Update(application); - } - - public void DeleteApplicationById(Guid id) - { - var a = GetApplicationById(id); - _context.Applications.Remove(a); - } -} diff --git a/src/Hippo/Repositories/DbChannelRepository.cs b/src/Hippo/Repositories/DbChannelRepository.cs deleted file mode 100644 index 690a1c465..000000000 --- a/src/Hippo/Repositories/DbChannelRepository.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Hippo.Models; -using Microsoft.EntityFrameworkCore; - -namespace Hippo.Repositories; - -public class DbChannelRepository : IChannelRepository -{ - private readonly DataContext _context; - - public DbChannelRepository(DataContext context) - { - _context = context; - } - - public Channel GetChannelByName(Application owner, string name) => - _context.Channels - .Where(c => c.Application == owner && c.Name == name) - .Include(c => c.Application) - .Include(c => c.Configuration) - .ThenInclude(c => c.EnvironmentVariables) - .Include(c => c.Domain) - .Include(c => c.ActiveRevision) - .Include(c => c.SpecifiedRevision) - .SingleOrDefault(); - - public Channel GetChannelById(Guid id) => - _context.Channels - .Where(c => c.Id == id) - .Include(c => c.Application) - .Include(c => c.Configuration) - .ThenInclude(c => c.EnvironmentVariables) - .Include(c => c.Domain) - .Include(c => c.ActiveRevision) - .Include(c => c.SpecifiedRevision) - .SingleOrDefault(); - - public async Task AddNew(Channel channel) - { - // TODO: remove once we sort out the ports stuff - try - { - var lastPort = _context.Channels.Max(c => c.PortID); - channel.PortID = lastPort + 1; - } - catch (InvalidOperationException) - { - channel.PortID = 0; - } - await _context.Channels.AddAsync(channel); - } - - public void DeleteChannelById(Guid id) - { - var c = GetChannelById(id); - _context.Channels.Remove(c); - } -} diff --git a/src/Hippo/Repositories/DbEventLogRepository.cs b/src/Hippo/Repositories/DbEventLogRepository.cs deleted file mode 100644 index 56a2f7ab7..000000000 --- a/src/Hippo/Repositories/DbEventLogRepository.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Hippo.Models; - -namespace Hippo.Repositories; - -public class DbEventLogRepository : IEventLogRepository -{ - private readonly DataContext _context; - private readonly ICurrentUser _user; - - public DbEventLogRepository(DataContext context, ICurrentUser user) - { - _context = context; - _user = user; - } - - public async Task LoginSucceeded(EventOrigin source, string userName) - { - var entry = new EventLogEntry - { - EventKind = EventKind.AccountLogin, - EventSource = source, - Timestamp = DateTime.UtcNow, - UserName = userName, - Description = "login succeeded", - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public async Task LoginFailed(EventOrigin source, string userName, string reason) - { - var entry = new EventLogEntry - { - EventKind = EventKind.AccountLoginFailed, - EventSource = source, - Timestamp = DateTime.UtcNow, - UserName = userName, - Description = reason, - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public async Task ChannelCreated(EventOrigin source, Channel channel) - { - var entry = new EventLogEntry - { - EventKind = EventKind.ChannelCreated, - EventSource = source, - Timestamp = DateTime.UtcNow, - ApplicationId = channel.Application.Id, - ChannelId = channel.Id, - UserName = _user.Name(), - Description = channel.ConfigurationSummary(), - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public async Task ChannelEdited(EventOrigin source, Channel channel) - { - var entry = new EventLogEntry - { - EventKind = EventKind.ChannelEdited, - EventSource = source, - Timestamp = DateTime.UtcNow, - ApplicationId = channel.Application.Id, - ChannelId = channel.Id, - UserName = _user.Name(), - Description = channel.ConfigurationSummary(), - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public async Task ChannelRevisionChanged(EventOrigin source, Channel channel, string oldRevision, string reason) - { - var entry = new EventLogEntry - { - EventKind = EventKind.ChannelRevisionChanged, - EventSource = source, - Timestamp = DateTime.UtcNow, - ApplicationId = channel.Application.Id, - ChannelId = channel.Id, - UserName = _user.Name(), - Description = $"changed from {oldRevision} to {channel.ActiveRevision?.RevisionNumber ?? "(none)"} because {reason}", - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public async Task ChannelDeleted(EventOrigin source, Guid channelId, Application application, string channelName) - { - var entry = new EventLogEntry - { - EventKind = EventKind.ChannelDeleted, - EventSource = source, - Timestamp = DateTime.UtcNow, - ApplicationId = application.Id, - ChannelId = channelId, - UserName = _user.Name(), - Description = $"deleted environment formerly known as {channelName}", - }; - await _context.EventLogEntries.AddAsync(entry); - } - - public IEnumerable GetRecentByApplication(Application application, int maxCount) - { - return _context.EventLogEntries - .Where(e => e.ApplicationId == application.Id) - .OrderByDescending(e => e.Timestamp) - .Take(maxCount); - } -} diff --git a/src/Hippo/Repositories/DbRevisionRepository.cs b/src/Hippo/Repositories/DbRevisionRepository.cs deleted file mode 100644 index 14ca015bf..000000000 --- a/src/Hippo/Repositories/DbRevisionRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Linq; -using Hippo.Models; - -namespace Hippo.Repositories; - -public class DbRevisionRepository : IRevisionRepository -{ - private readonly DataContext _context; - - public DbRevisionRepository(DataContext context) - { - _context = context; - } - - public Revision GetRevisionByNumber(Application owner, string revisionNumber) => - _context.Revisions - .Where(r => r.Application == owner && r.RevisionNumber == revisionNumber) - .SingleOrDefault(); -} diff --git a/src/Hippo/Repositories/DbUnitOfWork.cs b/src/Hippo/Repositories/DbUnitOfWork.cs deleted file mode 100644 index 4adca2326..000000000 --- a/src/Hippo/Repositories/DbUnitOfWork.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading.Tasks; -using Hippo.Models; - -namespace Hippo.Repositories; - -public class DbUnitOfWork : IUnitOfWork -{ - private readonly DataContext _dataContext; - - public DbUnitOfWork(DataContext dataContext, ICurrentUser currentUser) - { - _dataContext = dataContext; - - Accounts = new DbAccountRepository(_dataContext); - Applications = new DbApplicationRepository(_dataContext, currentUser); - Channels = new DbChannelRepository(_dataContext); - EventLog = new DbEventLogRepository(_dataContext, currentUser); - Revisions = new DbRevisionRepository(_dataContext); - } - - // We could make these lazy, but they are as cheap to construct as a Lazy would - // be, so why bother - public IAccountRepository Accounts { get; } - public IApplicationRepository Applications { get; } - public IChannelRepository Channels { get; } - public IEventLogRepository EventLog { get; } - public IRevisionRepository Revisions { get; } - - public async Task SaveChanges() => await _dataContext.SaveChangesAsync(); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _dataContext.Dispose(); - } - } -} diff --git a/src/Hippo/Repositories/IAccountRepository.cs b/src/Hippo/Repositories/IAccountRepository.cs deleted file mode 100644 index ed6a9acf4..000000000 --- a/src/Hippo/Repositories/IAccountRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hippo.Repositories; - -public interface IAccountRepository -{ - bool IsEmpty(); -} diff --git a/src/Hippo/Repositories/IApplicationRepository.cs b/src/Hippo/Repositories/IApplicationRepository.cs deleted file mode 100644 index e217541af..000000000 --- a/src/Hippo/Repositories/IApplicationRepository.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Hippo.Models; - -namespace Hippo.Repositories; - -public interface IApplicationRepository -{ - IEnumerable ListApplications(); - IEnumerable ListApplicationsForAllUsers(); - IEnumerable ListApplicationsByStorageId(string storageId); - Application GetApplicationById(Guid id); - bool ApplicationExistsById(Guid id); - Task AddNew(Application application); - void Update(Application application); - void DeleteApplicationById(Guid id); -} diff --git a/src/Hippo/Repositories/IChannelRepository.cs b/src/Hippo/Repositories/IChannelRepository.cs deleted file mode 100644 index 92c9d8830..000000000 --- a/src/Hippo/Repositories/IChannelRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; -using Hippo.Models; - -namespace Hippo.Repositories; - -public interface IChannelRepository -{ - Channel GetChannelByName(Application owner, string name); - Channel GetChannelById(Guid id); - Task AddNew(Channel channel); - void DeleteChannelById(Guid id); -} diff --git a/src/Hippo/Repositories/ICurrentUser.cs b/src/Hippo/Repositories/ICurrentUser.cs deleted file mode 100644 index 96a4b510b..000000000 --- a/src/Hippo/Repositories/ICurrentUser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Hippo.Repositories -{ - public interface ICurrentUser - { - string Name(); - } -} diff --git a/src/Hippo/Repositories/IEventLogRepository.cs b/src/Hippo/Repositories/IEventLogRepository.cs deleted file mode 100644 index b076fe2cb..000000000 --- a/src/Hippo/Repositories/IEventLogRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Hippo.Models; - -namespace Hippo.Repositories; - -public interface IEventLogRepository -{ - Task LoginSucceeded(EventOrigin source, string userName); - Task LoginFailed(EventOrigin source, string userName, string reason); - - Task ChannelCreated(EventOrigin source, Channel channel); - Task ChannelEdited(EventOrigin source, Channel channel); - Task ChannelRevisionChanged(EventOrigin source, Channel channel, string oldRevision, string reason); - Task ChannelDeleted(EventOrigin source, Guid channelId, Application application, string channelName); - - IEnumerable GetRecentByApplication(Application application, int maxCount); -} diff --git a/src/Hippo/Repositories/IRevisionRepository.cs b/src/Hippo/Repositories/IRevisionRepository.cs deleted file mode 100644 index ae4f6b1db..000000000 --- a/src/Hippo/Repositories/IRevisionRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Hippo.Models; - -namespace Hippo.Repositories; - -public interface IRevisionRepository -{ - Revision GetRevisionByNumber(Application owner, string revisionNumber); -} diff --git a/src/Hippo/Repositories/IUnitOfWork.cs b/src/Hippo/Repositories/IUnitOfWork.cs deleted file mode 100644 index b16f7865a..000000000 --- a/src/Hippo/Repositories/IUnitOfWork.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Hippo.Repositories; - -public interface IUnitOfWork : IDisposable -{ - IAccountRepository Accounts { get; } - IApplicationRepository Applications { get; } - IChannelRepository Channels { get; } - IEventLogRepository EventLog { get; } - IRevisionRepository Revisions { get; } - - Task SaveChanges(); -} diff --git a/src/Hippo/Schedulers/JobScheduler.cs b/src/Hippo/Schedulers/JobScheduler.cs deleted file mode 100644 index d3ad8a8f2..000000000 --- a/src/Hippo/Schedulers/JobScheduler.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using Hippo.Models; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Hippo.Schedulers; - -public class ChannelStartedEventArgs : EventArgs -{ - public Channel Channel { get; set; } - public string ListenAddress { get; set; } -} - -public abstract class JobScheduler -{ - private protected readonly ILogger _logger; - private protected readonly string _bindleUrl; - private const string ENV_BINDLE = "BINDLE_URL"; - - public event EventHandler ChannelStarted; - public event EventHandler ChannelStopped; - - private protected JobScheduler(ILogger logger, IHostEnvironment env) - { - _logger = logger; - - _bindleUrl = Environment.GetEnvironmentVariable(ENV_BINDLE); - if (string.IsNullOrWhiteSpace(_bindleUrl)) - { - _logger.LogError($"Bindle server URL not specified: set {ENV_BINDLE} environment variable"); - if (!env.IsDevelopment()) - { - throw new ArgumentException($"No Channels will be able to run - this scheduler requires {ENV_BINDLE} environment variable to run"); - } - } - } - - public virtual void OnSchedulerStart(IEnumerable applications) - { - foreach (var application in applications) - { - foreach (var channel in application.Channels) - { - if (channel.ActiveRevision == null) - { - _logger.LogWarning($"Scheduler start: Skipping channel {channel.Name} in application {application.Name}: no active revision"); - } - else - { - _logger.LogTrace($"Scheduler start: Starting channel {channel.Name} in application {application.Name}"); - try - { - Start(channel); - } - catch (Exception e) - { - _logger.LogWarning($"Scheduler start: Error starting channel {channel.Name} in application {application.Name}: {e}"); - } - } - } - } - } - - public abstract void Start(Channel c); - - public abstract void Stop(Channel c); - - private protected virtual void OnChannelStarted(ChannelStartedEventArgs e) - { - ChannelStarted?.Invoke(this, e); - } - - private protected virtual void OnChannelStopped(Channel c) - { - ChannelStopped?.Invoke(this, c); - } - -} diff --git a/src/Hippo/Schedulers/WagiDotnetJobScheduler.cs b/src/Hippo/Schedulers/WagiDotnetJobScheduler.cs deleted file mode 100644 index 9da0179e1..000000000 --- a/src/Hippo/Schedulers/WagiDotnetJobScheduler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Hippo.Config; -using Hippo.Models; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Hippo.Schedulers; - -public class WagiDotnetJobScheduler : JobScheduler -{ - private readonly IChannelConfigurationProvider _channelConfigurationProvider; - public WagiDotnetJobScheduler(ILogger logger, IChannelConfigurationProvider channelConfigurationProvider, IHostEnvironment env) - : base(logger, env) - { - _channelConfigurationProvider = channelConfigurationProvider; - _channelConfigurationProvider.SetBindleServer(_bindleUrl); - } - - public override void Start(Channel c) - { - var port = c.PortID + Channel.EphemeralPortRange; - var listenAddress = $"http://127.0.0.1:{port}"; - _channelConfigurationProvider.AddChannel(c, listenAddress); - var data = new ChannelStartedEventArgs(); - data.Channel = c; - data.ListenAddress = listenAddress; - OnChannelStarted(data); - } - - public override void Stop(Channel c) - { - OnChannelStopped(c); - _channelConfigurationProvider.RemoveChannel(c); - - } -} diff --git a/src/Hippo/Tasks/ChannelUpdateBackgroundService.cs b/src/Hippo/Tasks/ChannelUpdateBackgroundService.cs deleted file mode 100644 index 48951ee46..000000000 --- a/src/Hippo/Tasks/ChannelUpdateBackgroundService.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Hippo.Repositories; -using Hippo.Schedulers; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Hippo.Tasks; - -public class ChannelUpdateBackgroundService : BackgroundService -{ - private readonly IServiceProvider _services; - private readonly ITaskQueue _queue; - private readonly ILogger _logger; - - public ChannelUpdateBackgroundService(IServiceProvider services, ITaskQueue queue, ILogger logger) - { - _services = services; - _queue = queue; - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - var channelReference = await _queue.Dequeue(stoppingToken); - _logger.LogTrace($"ExecuteAsync: dequeued app {channelReference.ApplicationId}, channel {channelReference.ChannelId}"); - using (var scope = _services.CreateScope()) - { - using (var unitOfWork = scope.ServiceProvider.GetRequiredService()) - { - try - { - var channel = unitOfWork.Channels.GetChannelById(channelReference.ChannelId); - // TODO: should we make this responsible for updating the active revision - var scheduler = scope.ServiceProvider.GetRequiredService(); - // TODO: do any schedulers need the channel info *before* ActiveRevision - // got updated in order to stop? - try - { - _logger.LogInformation($"ExecuteAsync: stopping {channel.Application.Name} channel {channel.Name}"); - scheduler.Stop(channel); - if (channel.ActiveRevision == null) - { - _logger.LogWarning($"ExecuteAsync: not restarting {channel.Application.Name} channel {channel.Name}: no active revision"); - } - else - { - _logger.LogInformation($"ExecuteAsync: restarting {channel.Application.Name} channel {channel.Name} at rev {channel.ActiveRevision.RevisionNumber}"); - scheduler.Start(channel); - _logger.LogTrace($"ExecuteAsync: redeployed {channel.Application.Name} channel {channel.Name} at rev {channel.ActiveRevision}"); - } - } - catch (Exception e) - { - // Catch here to provide more informative error message - _logger.LogError($"ExecuteAsync: failed to redeploy {channel.Application.Name} channel {channel.Name} at rev {channel.ActiveRevision}: {e}"); - } - } - catch (Exception e) - { - _logger.LogError($"ChannelUpdateTask: error processing {channelReference.ChannelId}: {e}"); - } - // TODO: should failed channels be retried or put on a manual queue or something? - } - } - } - } -} - -public record ChannelReference(Guid ApplicationId, Guid ChannelId); diff --git a/src/Hippo/Tasks/ReverseProxyUpdateBackgroundService.cs b/src/Hippo/Tasks/ReverseProxyUpdateBackgroundService.cs deleted file mode 100644 index 9b393ad26..000000000 --- a/src/Hippo/Tasks/ReverseProxyUpdateBackgroundService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Hippo.Proxies; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Hippo.Tasks; - -public class ReverseProxyUpdateBackgroundService : BackgroundService -{ - private readonly IReverseProxyUpdater _reverseProxyUpdater; - private readonly ITaskQueue _queue; - private readonly ILogger _logger; - - public ReverseProxyUpdateBackgroundService(IReverseProxyUpdater reverseProxyUpdater, ITaskQueue queue, ILogger logger) - { - _reverseProxyUpdater = reverseProxyUpdater; - _queue = queue; - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - var configUpdated = false; - var moreRecords = false; - var reverseProxyUpdateRequest = await _queue.Dequeue(stoppingToken); - do - { - try - { - _logger.LogTrace($"ExecuteAsync: Dequeued Proxy Update Request AppId: {reverseProxyUpdateRequest.ApplicationId}, ChannelId: {reverseProxyUpdateRequest.ChannelId} Domain: {reverseProxyUpdateRequest.Domain} Action: {reverseProxyUpdateRequest.Action}"); - var updated = _reverseProxyUpdater.UpdateProxyRecord(reverseProxyUpdateRequest); - configUpdated = configUpdated || updated; - (moreRecords, reverseProxyUpdateRequest) = _queue.TryRead(); - } - catch (Exception e) - { - _logger.LogError($"ReverseProxyUpdateTask: error processing AppId: {reverseProxyUpdateRequest.ApplicationId}, ChannelId: {reverseProxyUpdateRequest.ChannelId} Domain: {reverseProxyUpdateRequest.Domain}: {e}"); - } - } while (moreRecords); - - if (configUpdated) - { - _reverseProxyUpdater.UpdateConfig(); - } - - } - - _logger.LogTrace("ExecuteAsync: ReverseProxyUpdateRequest - Cancellation Requested"); - } -} - -public enum ReverseProxyAction -{ - Start, - Stop -} - -public record ReverseProxyUpdateRequest(Guid ApplicationId, Guid ChannelId, string Host, string Domain, ReverseProxyAction Action); diff --git a/src/Hippo/ViewModels/AccountRegisterForm.cs b/src/Hippo/ViewModels/AccountRegisterForm.cs deleted file mode 100644 index d1add604c..000000000 --- a/src/Hippo/ViewModels/AccountRegisterForm.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class AccountRegisterForm : ITraceable -{ - [Required] - [Display(Name = "Username")] - public string UserName { get; set; } - - [DataType(DataType.EmailAddress)] - [Display(Name = "Email address")] - public string Email { get; set; } - - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } - - [Required] - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "Password and confirmation password do not match")] - public string PasswordConfirm { get; set; } - - public string FormatTrace() => - $"{nameof(AccountRegisterForm)}[username={UserName}]"; -} diff --git a/src/Hippo/ViewModels/ApiLoginForm.cs b/src/Hippo/ViewModels/ApiLoginForm.cs deleted file mode 100644 index 722132ab0..000000000 --- a/src/Hippo/ViewModels/ApiLoginForm.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class ApiLoginForm : ITraceable -{ - [Required] - [Display(Name = "username")] - public string UserName { get; set; } - - [Required] - [Display(Name = "password")] - [DataType(DataType.Password)] - public string Password { get; set; } - - public string FormatTrace() => - $"{nameof(ApiLoginForm)}[username={UserName}]"; -} diff --git a/src/Hippo/ViewModels/AppDetails.cs b/src/Hippo/ViewModels/AppDetails.cs deleted file mode 100644 index fec4b3c4d..000000000 --- a/src/Hippo/ViewModels/AppDetails.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Hippo.Models; - -namespace Hippo.ViewModels; - -public class AppDetails -{ - public Application Application { get; set; } - public IReadOnlyCollection Channels { get; set; } - public IReadOnlyCollection Revisions { get; set; } - public IReadOnlyCollection RecentActivity { get; set; } - - public string ChannelNameFor(EventLogEntry evt) - { - if (evt.ChannelId.HasValue) - { - return Channels.FirstOrDefault(c => c.Id == evt.ChannelId)?.Name ?? evt.ChannelId?.ToString(); - } - return string.Empty; - } -} diff --git a/src/Hippo/ViewModels/AppEditChannelForm.cs b/src/Hippo/ViewModels/AppEditChannelForm.cs deleted file mode 100644 index 9704d572c..000000000 --- a/src/Hippo/ViewModels/AppEditChannelForm.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace Hippo.ViewModels; - -public class AppEditChannelForm : ITraceable -{ - [Required] - public Guid ApplicationId { get; set; } - [Required] - public Guid ChannelId { get; set; } - public string ChannelName { get; set; } - - - [Display(Name = "Desired revision selection strategy")] - public string SelectedRevisionSelectionStrategy { get; set; } - public IEnumerable RevisionSelectionStrategies { get; set; } - - [Display(Name = "Revision to update it to (if UseSpecifiedRevision)")] - public string SelectedRevisionNumber { get; set; } - public IEnumerable Revisions { get; set; } - - [Display(Name = "Revision rule (if UseRangeRule)")] - public string SelectedRevisionRule { get; set; } - - [Display(Name = "Environment variables (name=value separated by semicolon or newline")] - public string EnvironmentVariables { get; set; } - - [Display(Name = "Domain name")] - public string Domain { get; set; } - - public string FormatTrace() => - $"{GetType().Name}[appid={ApplicationId}, chanid={ChannelId}, rev={SelectedRevisionNumber}]"; -} diff --git a/src/Hippo/ViewModels/AppEditForm.cs b/src/Hippo/ViewModels/AppEditForm.cs deleted file mode 100644 index 0efcb5de6..000000000 --- a/src/Hippo/ViewModels/AppEditForm.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class AppEditForm : ITraceable, IValidatableObject -{ - [Required] - public Guid Id { get; set; } - - [Required] - public string Name { get; set; } - - [Required] - [Display(Name = "Bindle name")] - public string StorageId { get; set; } - - [Required] - public string Collaborators { get; set; } - - public string FormatTrace() => - $"{nameof(AppEditForm)}[id={Id}, name={Name}, stg={StorageId}]"; - - public IEnumerable Validate(ValidationContext validationContext) - { - if (string.IsNullOrEmpty(Name)) - { - yield return new ValidationResult( - "The application name must not be empty", - new[] { nameof(Name) } - ); - } - - if (string.IsNullOrEmpty(StorageId)) - { - yield return new ValidationResult( - "The bindle name must not be empty", - new[] { nameof(StorageId) } - ); - } - } - - public ICollection ParseCollaborators() - { - if (string.IsNullOrWhiteSpace(Collaborators)) - { - return Array.Empty(); - } - - return Collaborators.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } -} diff --git a/src/Hippo/ViewModels/AppNewChannelForm.cs b/src/Hippo/ViewModels/AppNewChannelForm.cs deleted file mode 100644 index d97e2a8d9..000000000 --- a/src/Hippo/ViewModels/AppNewChannelForm.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Hippo.Logging; -using Hippo.Models; -using Hippo.OperationData; -using Hippo.Rules; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace Hippo.ViewModels; - -public sealed class AppNewChannelForm : ITraceable, IValidatableObject, ICreateChannelParameters -{ - [Required] - public Guid Id { get; set; } - - [Display(Name = "Channel name")] - [Required] - public string ChannelName { get; set; } - - [Display(Name = "Desired revision selection strategy")] - public string SelectedRevisionSelectionStrategy { get; set; } - public IEnumerable RevisionSelectionStrategies { get; set; } - - [Display(Name = "Revision to update it to (if UseSpecifiedRevision)")] - public string SelectedRevisionNumber { get; set; } - public IEnumerable Revisions { get; set; } - - [Display(Name = "Revision rule (if UseRangeRule)")] - public string SelectedRevisionRule { get; set; } - - [Display(Name = "Environment variables (name=value separated by semicolon or newline")] - public string EnvironmentVariables { get; set; } - - [Display(Name = "Domain name")] - public string Domain { get; set; } - - public string FormatTrace() => - $"{GetType().Name}[id={Id}, rev={SelectedRevisionNumber}, chan={ChannelName}]"; - - public IEnumerable Validate(ValidationContext validationContext) - { - if (string.IsNullOrWhiteSpace(SelectedRevisionSelectionStrategy)) - { - yield return new ValidationResult("Must select a revision strategy", new[] { nameof(SelectedRevisionSelectionStrategy) }); - } - - if (SelectedRevisionSelectionStrategy == Enum.GetName(ChannelRevisionSelectionStrategy.UseSpecifiedRevision)) - { - if (string.IsNullOrWhiteSpace(SelectedRevisionNumber)) - { - yield return new ValidationResult( - $"Revision number must be specified when fixing a channel to a revision number", - new[] { nameof(SelectedRevisionNumber) }); - } - - if (!SemVer.Version.TryParse(SelectedRevisionNumber, out _)) - { - yield return new ValidationResult( - $"Revision number is not in a valid format", - new[] { nameof(SelectedRevisionNumber) }); - } - - } - - if (SelectedRevisionSelectionStrategy == Enum.GetName(ChannelRevisionSelectionStrategy.UseRangeRule)) - { - if (string.IsNullOrWhiteSpace(SelectedRevisionRule)) - { - yield return new ValidationResult( - $"Revision range rule must be specified when not fixing a channel to a revision number", - new[] { nameof(SelectedRevisionRule) }); - } - - - var ruleError = RevisionRangeRule.Validate(SelectedRevisionRule); - if (ruleError != null) - { - yield return new ValidationResult( - $"Revision range rule is not valid rule syntax: {ruleError.Message}", - new[] { nameof(SelectedRevisionRule) }); - } - - } - - if (!string.IsNullOrWhiteSpace(EnvironmentVariables)) - { - var entries = EnvironmentVariables.Split('\n', ';').Select(s => s.Trim()); - var invalidEntries = entries.Where(e => !IsValidEnvVar(e)); - foreach (var invalidEntry in invalidEntries) - { - yield return new ValidationResult($"'{invalidEntry} is not in a valid format for an environment variable", new[] { nameof(EnvironmentVariables) }); - } - } - - // TODO: validate domain - } - - private static bool IsValidEnvVar(string entry) - { - if (string.IsNullOrWhiteSpace(entry)) - { - return false; - } - var bits = entry.Split('='); - return bits.Length == 2 && !string.IsNullOrWhiteSpace(bits[0]) && !string.IsNullOrWhiteSpace(bits[1]); - } - - private static Dictionary ParseEnvironmentVariables(string text) - { - // TODO: assumes validation in web form - should not assume this - if (string.IsNullOrWhiteSpace(text)) - { - return new(); - } - - return text.Split('\n', ';') - .Select(e => e.Trim()) - .Select(e => e.Split('=')) - .ToDictionary(bits => bits[0], bits => bits[1]); - } - - // Adapters for ICreateChannelParameters - Guid ICreateChannelParameters.ApplicationId => Id; - string ICreateChannelParameters.DomainName => Domain; - Dictionary ICreateChannelParameters.EnvironmentVariables => - ParseEnvironmentVariables(EnvironmentVariables); - ChannelRevisionSelectionStrategy ICreateChannelParameters.RevisionSelectionStrategy => - Enum.Parse(SelectedRevisionSelectionStrategy); - string ICreateChannelParameters.RevisionNumber => SelectedRevisionNumber; - string ICreateChannelParameters.RangeRule => SelectedRevisionRule; -} diff --git a/src/Hippo/ViewModels/AppNewForm.cs b/src/Hippo/ViewModels/AppNewForm.cs deleted file mode 100644 index a9a2d0be6..000000000 --- a/src/Hippo/ViewModels/AppNewForm.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Hippo.OperationData; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public sealed class AppNewForm : ITraceable, IValidatableObject, ICreateApplicationParameters -{ - [Required] - public string Name { get; set; } - - [Required] - [Display(Name = "Bindle name")] - public string StorageId { get; set; } - - public string FormatTrace() => - $"{nameof(AppNewForm)}[name={Name},stg={StorageId}]"; - - public IEnumerable Validate(ValidationContext validationContext) - { - if (string.IsNullOrEmpty(Name)) - { - yield return new ValidationResult( - "The application name must not be empty", - new[] { nameof(Name) } - ); - } - - if (string.IsNullOrEmpty(StorageId)) - { - yield return new ValidationResult( - "The bindle name must not be empty", - new[] { nameof(StorageId) } - ); - } - } - - // Adapter - string ICreateApplicationParameters.ApplicationName => Name; -} diff --git a/src/Hippo/ViewModels/AppRegisterRevisionForm.cs b/src/Hippo/ViewModels/AppRegisterRevisionForm.cs deleted file mode 100644 index ad256e7c9..000000000 --- a/src/Hippo/ViewModels/AppRegisterRevisionForm.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class AppRegisterRevisionForm : ITraceable -{ - [Required] - public Guid Id { get; set; } - - [Display(Name = "Revision to register")] - public string RevisionNumber { get; set; } - - public string FormatTrace() => - $"{nameof(AppRegisterRevisionForm)}[id={Id}]"; -} diff --git a/src/Hippo/ViewModels/Converters.cs b/src/Hippo/ViewModels/Converters.cs deleted file mode 100644 index efb2b7b31..000000000 --- a/src/Hippo/ViewModels/Converters.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace Hippo.ViewModels; - -internal static class Converters -{ - internal static IList AsSelectList(this IEnumerable source, Func selector) - { - return source.Select(o => new SelectListItem(selector(o), selector(o))).ToList(); - } - - internal static IList EnumValuesAsSelectList() - where T : struct, Enum - { - return Enum.GetValues().Select(o => new SelectListItem(Enum.GetName(o), Enum.GetName(o))).ToList(); - } -} diff --git a/src/Hippo/ViewModels/LoginForm.cs b/src/Hippo/ViewModels/LoginForm.cs deleted file mode 100644 index 94c0f7d46..000000000 --- a/src/Hippo/ViewModels/LoginForm.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class LoginForm : ITraceable -{ - [Required] - [Display(Name = "Username")] - public string UserName { get; set; } - - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } - - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } - - public string FormatTrace() => - $"{nameof(LoginForm)}[username={UserName}]"; -} diff --git a/src/Hippo/ViewModels/RevisionRegistrationForm.cs b/src/Hippo/ViewModels/RevisionRegistrationForm.cs deleted file mode 100644 index c8a0cdb78..000000000 --- a/src/Hippo/ViewModels/RevisionRegistrationForm.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Hippo.Logging; - -namespace Hippo.ViewModels; - -public class RevisionRegistrationForm : ITraceable -{ - [Required] - public Guid AppId { get; set; } - - [Required] - public string RevisionNumber { get; set; } - - public string FormatTrace() - => $"{nameof(RevisionRegistrationForm)}[appid={AppId}, rev={RevisionNumber}]"; -} diff --git a/src/Hippo/Views/Admin/Terminal.cshtml b/src/Hippo/Views/Admin/Terminal.cshtml deleted file mode 100644 index 9e30a40fa..000000000 --- a/src/Hippo/Views/Admin/Terminal.cshtml +++ /dev/null @@ -1,53 +0,0 @@ -
- -@section Styles { - @{await Html.RenderPartialAsync("_TerminalStylesPartial");} -} - -@section Scripts { - @{await Html.RenderPartialAsync("_TerminalScriptsPartial");} - -} diff --git a/src/Hippo/Views/App/Details.cshtml b/src/Hippo/Views/App/Details.cshtml deleted file mode 100644 index 2bd0965f9..000000000 --- a/src/Hippo/Views/App/Details.cshtml +++ /dev/null @@ -1,192 +0,0 @@ -@model Hippo.ViewModels.AppDetails - -@{ - ViewData["Title"] = @Html.DisplayFor(model => model.Application.Name); - Layout = "_AppLayout"; -} - -
- -
-
-
-
-
- Showing all activity. -
-
-
-
-
-
- @if (Model.Revisions.Any()) { -
    - @foreach (var revision in Model.Revisions.OrderByDescending(r => r.OrderKey())) { -
  • - @if (revision.ActiveOn().Any()) { - - @Html.DisplayFor(modelItem => revision.RevisionNumber) was deployed to @string.Join(", ", revision.ActiveOn().Select(c => c.Name)) on @Html.DisplayFor(modelItem => revision.Modified). - - } else { - - @Html.DisplayFor(modelItem => revision.RevisionNumber) was pushed on @Html.DisplayFor(modelItem => revision.Modified). - - } - -
    -@Html.DisplayFor(modelItem => revision)
    -                                
    -
  • - } -
- } else { -

No versions of your app are available to deploy yet.

-

You need to hippo push via the CLI. Check out the docs for details.

- } -
-
- - -
- - @foreach (var item in Model.Channels) { -
-
-
-
-
-

@Html.DisplayFor(modelItem => item.Name)

-
-
-
- @if (item.RangeRule != null) { -

- Auto deploying: - @Html.DisplayFor(modelItem => item.RangeRule) new versions. -

- } else { -

- Deployment locked to: - Version @Html.DisplayFor(modelItem => item.SpecifiedRevision.RevisionNumber). -

- } - Edit -
-
-
-
-
- -
-
- @if (Model.Revisions.Any()) { -
    - @* @foreach (var revision in Model.Revisions.OrderByDescending(r => r.OrderKey()).Where( revision.ActiveOn() == item.Name )) { *@ - @foreach (var revision in Model.Revisions.OrderByDescending(r => r.OrderKey())) { -
  • - @if (revision.ActiveOn().Any()) { - - @Html.DisplayFor(modelItem => revision.RevisionNumber) was deployed to @string.Join(", ", revision.ActiveOn().Select(c => c.Name)) on @Html.DisplayFor(modelItem => revision.Modified). - - } else { - - @Html.DisplayFor(modelItem => revision.RevisionNumber) was pushed on @Html.DisplayFor(modelItem => revision.Modified). - - } - -
    -@Html.DisplayFor(modelItem => revision)
    -                                    
    -
  • - } -
- } else { -

No versions of your app are available to deploy yet.

-

You need to hippo push via the CLI. Check out the docs for details.

- } -
-
- - -
- } - -
-
-
-

Application

- - - - - - - - - - - - - - - - -
@Html.DisplayNameFor(model => model.Application.Name)@Html.DisplayNameFor(model => model.Application.Created)@Html.DisplayNameFor(model => model.Application.Modified)
@Html.DisplayFor(model => model.Application.Name)@Html.DisplayFor(model => model.Application.Created)@Html.DisplayFor(model => model.Application.Modified)
- -

History

- - - - - - - - - - - - - @foreach (var item in Model.RecentActivity) { - - - - - - - - } - -
WhenWhatChannelWhatWho
@item.Timestamp@item.EventKind@Model.ChannelNameFor(item)@item.Description@item.UserName
- -
-
-
-
diff --git a/src/Hippo/Views/App/NewChannel.cshtml b/src/Hippo/Views/App/NewChannel.cshtml deleted file mode 100644 index a7482bdd7..000000000 --- a/src/Hippo/Views/App/NewChannel.cshtml +++ /dev/null @@ -1,135 +0,0 @@ -@model Hippo.ViewModels.AppNewChannelForm - -@{ - ViewData["Title"] = "Add an Environment"; -} - -
-
- -
- -
- -
-
-
- -
-
-
- -

-
-
-
-
-
- -
-
-
- -

-
-
-
-
-
- -
- -
-
-
- -
-
-
- -

-
-
-
- -
- -
-
- @foreach (var strategy in Model.RevisionSelectionStrategies) { - - } -

- -
-
- -
-
-
-
-
- -
-
-

-
-
-
- -
-
- -
-
-
- -

You can choose to deply all versions of your app (enter an *) or limit deploys to a specific range.

- -

Examples:
- To deploy minor releases that follow v1.0, set the range to 1<. To follow v1.0 but exclude releases beyond v2.0, set the range to 1-2.

-

-
-
-
-
-
-
-
-
-
- - Cancel -
-
-
-
-
-
- -
-
-
-
-

-
-
-
-
-
-
-
- -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/src/Hippo/Views/Home/Index.cshtml b/src/Hippo/Views/Home/Index.cshtml deleted file mode 100644 index e6f4f5224..000000000 --- a/src/Hippo/Views/Home/Index.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - ViewData["Title"] = "Home"; -} diff --git a/src/Hippo/Views/Shared/_TerminalScriptsPartial.cshtml b/src/Hippo/Views/Shared/_TerminalScriptsPartial.cshtml deleted file mode 100644 index 78ee13c0e..000000000 --- a/src/Hippo/Views/Shared/_TerminalScriptsPartial.cshtml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Hippo/Views/Shared/_TerminalStylesPartial.cshtml b/src/Hippo/Views/Shared/_TerminalStylesPartial.cshtml deleted file mode 100644 index 3d4cf146f..000000000 --- a/src/Hippo/Views/Shared/_TerminalStylesPartial.cshtml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Hippo/Views/_ViewImports.cshtml b/src/Hippo/Views/_ViewImports.cshtml deleted file mode 100644 index 4f48ff100..000000000 --- a/src/Hippo/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using Hippo.Models; -@using Hippo.ViewModels; -@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" diff --git a/src/Hippo/WagiDotnet/WagiDotnetStartup.cs b/src/Hippo/WagiDotnet/WagiDotnetStartup.cs deleted file mode 100644 index f4c7f23b1..000000000 --- a/src/Hippo/WagiDotnet/WagiDotnetStartup.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Deislabs.Wagi.Extensions; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Hippo.WagiDotnet; - -public class WagiDotnetStartup -{ - public WagiDotnetStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - // WASI Experimental HTTP requires HttpClient. - services.AddHttpClient(); - services.AddWagi(Configuration); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapWagiModules(); - }); - } -} diff --git a/src/Hippo/WagiDotnet/appsettings.Development.json b/src/Hippo/WagiDotnet/appsettings.Development.json deleted file mode 100644 index 394cb79a9..000000000 --- a/src/Hippo/WagiDotnet/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Deislabs.Wagi.DataSource" : "Trace" - } - } -} \ No newline at end of file diff --git a/src/Hippo/WagiDotnet/appsettings.json b/src/Hippo/WagiDotnet/appsettings.json deleted file mode 100644 index 284193a93..000000000 --- a/src/Hippo/WagiDotnet/appsettings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://0.0.0.0:5312" - }, - "Https": { - "Url": "https://0.0.0.0:5313" - } - } - } -} diff --git a/src/Hippo/appsettings.Development.json b/src/Hippo/appsettings.Development.json deleted file mode 100644 index c7a73f4c3..000000000 --- a/src/Hippo/appsettings.Development.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Hippo": "Trace" - } - }, - "Jwt": { - "Key": "ceci n'est pas une jeton", - "Issuer": "localhost", - "Audience": "localhost" - }, - "Database": { - "Driver": "sqlite" - }, - "ProxyEnabled": true, - "ConnectionStrings": { - "Database": "Data Source=hippo.db;Cache=Shared" - } -} diff --git a/src/Infrastructure/Data/ApplicationDbContext.cs b/src/Infrastructure/Data/ApplicationDbContext.cs new file mode 100644 index 000000000..1cb092221 --- /dev/null +++ b/src/Infrastructure/Data/ApplicationDbContext.cs @@ -0,0 +1,85 @@ +using System.Reflection; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Common; +using Hippo.Core.Entities; +using Hippo.Infrastructure.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Infrastructure.Data; + +public class ApplicationDbContext : IdentityDbContext, IApplicationDbContext +{ + private readonly ICurrentUserService _currentUserService; + + private readonly IDateTime _dateTime; + + private readonly IDomainEventService _domainEventService; + + public ApplicationDbContext( + DbContextOptions options, + ICurrentUserService currentUserService, + IDateTime dateTime, + IDomainEventService domainEventService) : base(options) + { + _currentUserService = currentUserService; + _dateTime = dateTime; + _domainEventService = domainEventService; + } + + public DbSet Apps => Set(); + + public DbSet Channels => Set(); + + public DbSet Domains => Set(); + + public DbSet EnvironmentVariables => Set(); + + public DbSet Revisions => Set(); + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + foreach (var entry in ChangeTracker.Entries()) + { + switch (entry.State) + { + case EntityState.Added: + entry.Entity.CreatedBy = _currentUserService.UserId; + entry.Entity.Created = _dateTime.UtcNow; + break; + case EntityState.Modified: + entry.Entity.LastModifiedBy = _currentUserService.UserId; + entry.Entity.LastModified = _dateTime.UtcNow; + break; + } + } + + var events = ChangeTracker.Entries() + .Select(x => x.Entity.DomainEvents) + .SelectMany(x => x) + .Where(domainEvent => !domainEvent.IsPublished) + .ToArray(); + + var result = await base.SaveChangesAsync(cancellationToken); + + await DispatchEvents(events); + + return result; + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + + base.OnModelCreating(builder); + } + + private async Task DispatchEvents(DomainEvent[] events) + { + foreach (var @event in events) + { + @event.IsPublished = true; + await _domainEventService.Publish(@event); + } + } +} diff --git a/src/Infrastructure/Data/ApplicationDbContextSeed.cs b/src/Infrastructure/Data/ApplicationDbContextSeed.cs new file mode 100644 index 000000000..882878c7b --- /dev/null +++ b/src/Infrastructure/Data/ApplicationDbContextSeed.cs @@ -0,0 +1,17 @@ +using Hippo.Infrastructure.Identity; +using Microsoft.AspNetCore.Identity; + +namespace Hippo.Infrastructure.Data; + +public static class ApplicationDbContextSeed +{ + public static async Task SeedIdentityRolesAsync(UserManager userManager, RoleManager roleManager) + { + var administratorRole = new IdentityRole("Administrator"); + + if (roleManager.Roles.All(r => r.Name != administratorRole.Name)) + { + await roleManager.CreateAsync(administratorRole); + } + } +} diff --git a/src/Infrastructure/Data/Configurations/AppConfiguration.cs b/src/Infrastructure/Data/Configurations/AppConfiguration.cs new file mode 100644 index 000000000..9862c4b98 --- /dev/null +++ b/src/Infrastructure/Data/Configurations/AppConfiguration.cs @@ -0,0 +1,17 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Hippo.Infrastructure.Data.Configurations; + +public class AppConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Ignore(e => e.DomainEvents); + + builder.Property(a => a.Name) + .HasMaxLength(32) + .IsRequired(); + } +} diff --git a/src/Infrastructure/Data/Configurations/ChannelConfiguration.cs b/src/Infrastructure/Data/Configurations/ChannelConfiguration.cs new file mode 100644 index 000000000..91a5ebc26 --- /dev/null +++ b/src/Infrastructure/Data/Configurations/ChannelConfiguration.cs @@ -0,0 +1,13 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Hippo.Infrastructure.Data.Configurations; + +public class ChannelConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Ignore(e => e.DomainEvents); + } +} diff --git a/src/Infrastructure/Data/Configurations/DomainConfiguration.cs b/src/Infrastructure/Data/Configurations/DomainConfiguration.cs new file mode 100644 index 000000000..65843c2c5 --- /dev/null +++ b/src/Infrastructure/Data/Configurations/DomainConfiguration.cs @@ -0,0 +1,13 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Hippo.Infrastructure.Data.Configurations; + +public class DomainConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Ignore(e => e.DomainEvents); + } +} diff --git a/src/Infrastructure/Data/Configurations/EnvironmentVariableConfiguration.cs b/src/Infrastructure/Data/Configurations/EnvironmentVariableConfiguration.cs new file mode 100644 index 000000000..3d9635ff6 --- /dev/null +++ b/src/Infrastructure/Data/Configurations/EnvironmentVariableConfiguration.cs @@ -0,0 +1,13 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Hippo.Infrastructure.Data.Configurations; + +public class EnvironmentVariableConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Ignore(e => e.DomainEvents); + } +} diff --git a/src/Infrastructure/Data/Configurations/RevisionConfiguration.cs b/src/Infrastructure/Data/Configurations/RevisionConfiguration.cs new file mode 100644 index 000000000..251bfab2f --- /dev/null +++ b/src/Infrastructure/Data/Configurations/RevisionConfiguration.cs @@ -0,0 +1,13 @@ +using Hippo.Core.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Hippo.Infrastructure.Data.Configurations; + +public class RevisionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Ignore(e => e.DomainEvents); + } +} diff --git a/src/Hippo/Migrations/Sqlite/20210706011225_Initial.Designer.cs b/src/Infrastructure/Data/Migrations/20211210182727_Initial.Designer.cs similarity index 60% rename from src/Hippo/Migrations/Sqlite/20210706011225_Initial.Designer.cs rename to src/Infrastructure/Data/Migrations/20211210182727_Initial.Designer.cs index 8fd67a71f..7ef9f5325 100644 --- a/src/Hippo/Migrations/Sqlite/20210706011225_Initial.Designer.cs +++ b/src/Infrastructure/Data/Migrations/20211210182727_Initial.Designer.cs @@ -1,135 +1,56 @@ // using System; -using Hippo.Models; +using Hippo.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -namespace Hippo.Migrations.Sqlite +#nullable disable + +namespace Hippo.Infrastructure.Data.Migrations { - [DbContext(typeof(SqliteDataContext))] - [Migration("20210706011225_Initial")] + [DbContext(typeof(ApplicationDbContext))] + [Migration("20211210182727_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); - modelBuilder.Entity("Hippo.Models.Account", b => + modelBuilder.Entity("Hippo.Core.Entities.App", b => { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") + b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") + b.Property("Created") .HasColumnType("TEXT"); - b.Property("SigningKeyId") + b.Property("CreatedBy") .HasColumnType("TEXT"); - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) + b.Property("LastModified") .HasColumnType("TEXT"); - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LastModifiedBy") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - b.Property("Name") .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") + .HasMaxLength(32) .HasColumnType("TEXT"); b.Property("StorageId") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); + b.ToTable("Apps"); }); - modelBuilder.Entity("Hippo.Models.Channel", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -138,29 +59,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ActiveRevisionId") .HasColumnType("TEXT"); - b.Property("ApplicationId") + b.Property("AppId") .HasColumnType("TEXT"); - b.Property("ConfigurationId") + b.Property("Created") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("CreatedBy") + .HasColumnType("TEXT"); - b.Property("DomainId") + b.Property("LastModified") .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Name") .HasColumnType("TEXT"); - b.Property("PortID") + b.Property("PortId") .HasColumnType("INTEGER"); b.Property("RangeRule") @@ -169,164 +86,174 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("RevisionSelectionStrategy") .HasColumnType("INTEGER"); - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - b.HasKey("Id"); b.HasIndex("ActiveRevisionId"); - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); + b.HasIndex("AppId"); b.ToTable("Channels"); }); - modelBuilder.Entity("Hippo.Models.Configuration", b => + modelBuilder.Entity("Hippo.Core.Entities.Domain", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); + b.Property("ChannelId") + .HasColumnType("TEXT"); - b.ToTable("Configuration"); - }); + b.Property("Created") + .HasColumnType("TEXT"); - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("CreatedBy") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModified") + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Name") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("Name") + b.HasIndex("ChannelId") .IsUnique(); b.ToTable("Domains"); }); - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => + modelBuilder.Entity("Hippo.Core.Entities.EnvironmentVariable", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("ConfigurationId") + b.Property("ChannelId") .HasColumnType("TEXT"); b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); b.Property("Key") - .IsRequired() .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Value") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("ConfigurationId"); + b.HasIndex("ChannelId"); b.ToTable("EnvironmentVariables"); }); - modelBuilder.Entity("Hippo.Models.Key", b => + modelBuilder.Entity("Hippo.Core.Entities.Revision", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); + b.Property("AppId") + .HasColumnType("TEXT"); + b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("CreatedBy") + .HasColumnType("TEXT"); - b.Property("PrivateKey") - .IsRequired() + b.Property("LastModified") .HasColumnType("TEXT"); - b.Property("PublicKey") - .IsRequired() + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); + + b.Property("RevisionNumber") .HasColumnType("TEXT"); b.HasKey("Id"); - b.ToTable("Keys"); + b.HasIndex("AppId"); + + b.ToTable("Revisions"); }); - modelBuilder.Entity("Hippo.Models.Revision", b => + modelBuilder.Entity("Hippo.Infrastructure.Identity.Account", b => { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("Id") .HasColumnType("TEXT"); - b.Property("ApplicationId") + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); - b.Property("RevisionNumber") - .IsRequired() + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("ApplicationId"); + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); - b.ToTable("Revisions"); + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -352,7 +279,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AspNetRoles"); + b.ToTable("AspNetRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -375,7 +302,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims"); + b.ToTable("AspNetRoleClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -398,7 +325,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims"); + b.ToTable("AspNetUserClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -420,7 +347,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins"); + b.ToTable("AspNetUserLogins", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -435,7 +362,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles"); + b.ToTable("AspNetUserRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -454,80 +381,57 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); + b.ToTable("AspNetUserTokens", (string)null); }); - modelBuilder.Entity("Hippo.Models.Application", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") + b.HasOne("Hippo.Core.Entities.Revision", "ActiveRevision") .WithMany() .HasForeignKey("ActiveRevisionId"); - b.HasOne("Hippo.Models.Application", "Application") + b.HasOne("Hippo.Core.Entities.App", "App") .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("ActiveRevision"); - b.Navigation("Application"); - - b.Navigation("Configuration"); + b.Navigation("App"); + }); - b.Navigation("Domain"); + modelBuilder.Entity("Hippo.Core.Entities.Domain", b => + { + b.HasOne("Hippo.Core.Entities.Channel", "Channel") + .WithOne("Domain") + .HasForeignKey("Hippo.Core.Entities.Domain", "ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("SpecifiedRevision"); + b.Navigation("Channel"); }); - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => + modelBuilder.Entity("Hippo.Core.Entities.EnvironmentVariable", b => { - b.HasOne("Hippo.Models.Configuration", "Configuration") + b.HasOne("Hippo.Core.Entities.Channel", "Channel") .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Configuration"); + b.Navigation("Channel"); }); - modelBuilder.Entity("Hippo.Models.Revision", b => + modelBuilder.Entity("Hippo.Core.Entities.Revision", b => { - b.HasOne("Hippo.Models.Application", "Application") + b.HasOne("Hippo.Core.Entities.App", "App") .WithMany("Revisions") - .HasForeignKey("ApplicationId"); + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Application"); + b.Navigation("App"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -541,7 +445,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -550,7 +454,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -565,7 +469,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -574,24 +478,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("Hippo.Models.Application", b => + modelBuilder.Entity("Hippo.Core.Entities.App", b => { b.Navigation("Channels"); - b.Navigation("Collaborators"); - b.Navigation("Revisions"); }); - modelBuilder.Entity("Hippo.Models.Configuration", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { + b.Navigation("Domain"); + b.Navigation("EnvironmentVariables"); }); #pragma warning restore 612, 618 diff --git a/src/Hippo/Migrations/Sqlite/20210706011225_Initial.cs b/src/Infrastructure/Data/Migrations/20211210182727_Initial.cs similarity index 63% rename from src/Hippo/Migrations/Sqlite/20210706011225_Initial.cs rename to src/Infrastructure/Data/Migrations/20211210182727_Initial.cs index 1dd53affc..27292dd73 100644 --- a/src/Hippo/Migrations/Sqlite/20210706011225_Initial.cs +++ b/src/Infrastructure/Data/Migrations/20211210182727_Initial.cs @@ -1,109 +1,43 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; -namespace Hippo.Migrations.Sqlite +#nullable disable + +namespace Hippo.Infrastructure.Data.Migrations { public partial class Initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Configuration", + name: "Apps", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") - }, - constraints: table => - { - table.PrimaryKey("PK_Configuration", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Domains", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") - }, - constraints: table => - { - table.PrimaryKey("PK_Domains", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Keys", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - PublicKey = table.Column(type: "TEXT", nullable: false), - PrivateKey = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") - }, - constraints: table => - { - table.PrimaryKey("PK_Keys", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoleId = table.Column(type: "TEXT", nullable: false), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) + Name = table.Column(type: "TEXT", maxLength: 32, nullable: false), + StorageId = table.Column(type: "TEXT", nullable: true), + Created = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: true), + LastModified = table.Column(type: "TEXT", nullable: false), + LastModifiedBy = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + table.PrimaryKey("PK_Apps", x => x.Id); }); migrationBuilder.CreateTable( - name: "EnvironmentVariables", + name: "AspNetRoles", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), - ConfigurationId = table.Column(type: "TEXT", nullable: true), - Key = table.Column(type: "TEXT", nullable: false), - Value = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_EnvironmentVariables", x => x.Id); - table.ForeignKey( - name: "FK_EnvironmentVariables_Configuration_ConfigurationId", - column: x => x.ConfigurationId, - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + table.PrimaryKey("PK_AspNetRoles", x => x.Id); }); migrationBuilder.CreateTable( @@ -111,8 +45,6 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - SigningKeyId = table.Column(type: "TEXT", nullable: true), - ApplicationId = table.Column(type: "TEXT", nullable: true), UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), @@ -131,34 +63,50 @@ protected override void Up(MigrationBuilder migrationBuilder) constraints: table => { table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Revisions", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RevisionNumber = table.Column(type: "TEXT", nullable: true), + AppId = table.Column(type: "TEXT", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: true), + LastModified = table.Column(type: "TEXT", nullable: false), + LastModifiedBy = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Revisions", x => x.Id); table.ForeignKey( - name: "FK_AspNetUsers_Keys_SigningKeyId", - column: x => x.SigningKeyId, - principalTable: "Keys", + name: "FK_Revisions_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Applications", + name: "AspNetRoleClaims", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - StorageId = table.Column(type: "TEXT", nullable: false), - OwnerId = table.Column(type: "TEXT", nullable: true), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Applications", x => x.Id); + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); table.ForeignKey( - name: "FK_Applications_AspNetUsers_OwnerId", - column: x => x.OwnerId, - principalTable: "AspNetUsers", + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -247,89 +195,84 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "Revisions", + name: "Channels", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - ApplicationId = table.Column(type: "TEXT", nullable: true), - RevisionNumber = table.Column(type: "TEXT", nullable: false), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") + Name = table.Column(type: "TEXT", nullable: true), + RevisionSelectionStrategy = table.Column(type: "INTEGER", nullable: false), + RangeRule = table.Column(type: "TEXT", nullable: true), + ActiveRevisionId = table.Column(type: "TEXT", nullable: true), + PortId = table.Column(type: "INTEGER", nullable: false), + AppId = table.Column(type: "TEXT", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: true), + LastModified = table.Column(type: "TEXT", nullable: false), + LastModifiedBy = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Revisions", x => x.Id); + table.PrimaryKey("PK_Channels", x => x.Id); table.ForeignKey( - name: "FK_Revisions_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", + name: "FK_Channels_Apps_AppId", + column: x => x.AppId, + principalTable: "Apps", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Channels_Revisions_ActiveRevisionId", + column: x => x.ActiveRevisionId, + principalTable: "Revisions", + principalColumn: "Id"); }); migrationBuilder.CreateTable( - name: "Channels", + name: "Domains", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - RevisionSelectionStrategy = table.Column(type: "INTEGER", nullable: false), - SpecifiedRevisionId = table.Column(type: "TEXT", nullable: true), - RangeRule = table.Column(type: "TEXT", nullable: true), - ActiveRevisionId = table.Column(type: "TEXT", nullable: true), - ApplicationId = table.Column(type: "TEXT", nullable: true), - DomainId = table.Column(type: "TEXT", nullable: true), - PortID = table.Column(type: "INTEGER", nullable: false), - ConfigurationId = table.Column(type: "TEXT", nullable: true), - Created = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"), - Modified = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')") + ChannelId = table.Column(type: "TEXT", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: true), + LastModified = table.Column(type: "TEXT", nullable: false), + LastModifiedBy = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Channels", x => x.Id); - table.ForeignKey( - name: "FK_Channels_Applications_ApplicationId", - column: x => x.ApplicationId, - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Configuration_ConfigurationId", - column: x => x.ConfigurationId, - principalTable: "Configuration", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Channels_Domains_DomainId", - column: x => x.DomainId, - principalTable: "Domains", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + table.PrimaryKey("PK_Domains", x => x.Id); table.ForeignKey( - name: "FK_Channels_Revisions_ActiveRevisionId", - column: x => x.ActiveRevisionId, - principalTable: "Revisions", + name: "FK_Domains_Channels_ChannelId", + column: x => x.ChannelId, + principalTable: "Channels", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EnvironmentVariables", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Key = table.Column(type: "TEXT", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + ChannelId = table.Column(type: "TEXT", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: true), + LastModified = table.Column(type: "TEXT", nullable: false), + LastModifiedBy = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EnvironmentVariables", x => x.Id); table.ForeignKey( - name: "FK_Channels_Revisions_SpecifiedRevisionId", - column: x => x.SpecifiedRevisionId, - principalTable: "Revisions", + name: "FK_EnvironmentVariables_Channels_ChannelId", + column: x => x.ChannelId, + principalTable: "Channels", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateIndex( - name: "IX_Applications_Name", - table: "Applications", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Applications_OwnerId", - table: "Applications", - column: "OwnerId"); - migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", @@ -361,16 +304,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "AspNetUsers", column: "NormalizedEmail"); - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_SigningKeyId", - table: "AspNetUsers", - column: "SigningKeyId"); - migrationBuilder.CreateIndex( name: "UserNameIndex", table: "AspNetUsers", @@ -383,56 +316,29 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "ActiveRevisionId"); migrationBuilder.CreateIndex( - name: "IX_Channels_ApplicationId", - table: "Channels", - column: "ApplicationId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_ConfigurationId", + name: "IX_Channels_AppId", table: "Channels", - column: "ConfigurationId"); + column: "AppId"); migrationBuilder.CreateIndex( - name: "IX_Channels_DomainId", - table: "Channels", - column: "DomainId"); - - migrationBuilder.CreateIndex( - name: "IX_Channels_SpecifiedRevisionId", - table: "Channels", - column: "SpecifiedRevisionId"); - - migrationBuilder.CreateIndex( - name: "IX_Domains_Name", + name: "IX_Domains_ChannelId", table: "Domains", - column: "Name", + column: "ChannelId", unique: true); migrationBuilder.CreateIndex( - name: "IX_EnvironmentVariables_ConfigurationId", + name: "IX_EnvironmentVariables_ChannelId", table: "EnvironmentVariables", - column: "ConfigurationId"); + column: "ChannelId"); migrationBuilder.CreateIndex( - name: "IX_Revisions_ApplicationId", + name: "IX_Revisions_AppId", table: "Revisions", - column: "ApplicationId"); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Applications_ApplicationId", - table: "AspNetUsers", - column: "ApplicationId", - principalTable: "Applications", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + column: "AppId"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_Applications_AspNetUsers_OwnerId", - table: "Applications"); - migrationBuilder.DropTable( name: "AspNetRoleClaims"); @@ -449,7 +355,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "AspNetUserTokens"); migrationBuilder.DropTable( - name: "Channels"); + name: "Domains"); migrationBuilder.DropTable( name: "EnvironmentVariables"); @@ -458,22 +364,16 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "AspNetRoles"); migrationBuilder.DropTable( - name: "Domains"); - - migrationBuilder.DropTable( - name: "Revisions"); - - migrationBuilder.DropTable( - name: "Configuration"); + name: "AspNetUsers"); migrationBuilder.DropTable( - name: "AspNetUsers"); + name: "Channels"); migrationBuilder.DropTable( - name: "Applications"); + name: "Revisions"); migrationBuilder.DropTable( - name: "Keys"); + name: "Apps"); } } } diff --git a/src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.Designer.cs b/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 56% rename from src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.Designer.cs rename to src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 552ee3d52..4116f60c3 100644 --- a/src/Hippo/Migrations/Sqlite/20210722051132_Timestamp.Designer.cs +++ b/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,135 +1,54 @@ // using System; -using Hippo.Models; +using Hippo.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -namespace Hippo.Migrations.Sqlite +#nullable disable + +namespace Hippo.Infrastructure.Data.Migrations { - [DbContext(typeof(SqliteDataContext))] - [Migration("20210722051132_Timestamp")] - partial class Timestamp + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot { - protected override void BuildTargetModel(ModelBuilder modelBuilder) + protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.7"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); - modelBuilder.Entity("Hippo.Models.Account", b => + modelBuilder.Entity("Hippo.Core.Entities.App", b => { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") + b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") + b.Property("Created") .HasColumnType("TEXT"); - b.Property("SigningKeyId") + b.Property("CreatedBy") .HasColumnType("TEXT"); - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) + b.Property("LastModified") .HasColumnType("TEXT"); - b.HasKey("Id"); - - b.HasIndex("ApplicationId"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("SigningKeyId"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Hippo.Models.Application", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("LastModifiedBy") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - b.Property("Name") .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OwnerId") + .HasMaxLength(32) .HasColumnType("TEXT"); b.Property("StorageId") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("OwnerId"); - - b.ToTable("Applications"); + b.ToTable("Apps"); }); - modelBuilder.Entity("Hippo.Models.Channel", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -138,29 +57,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ActiveRevisionId") .HasColumnType("TEXT"); - b.Property("ApplicationId") + b.Property("AppId") .HasColumnType("TEXT"); - b.Property("ConfigurationId") + b.Property("Created") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("CreatedBy") + .HasColumnType("TEXT"); - b.Property("DomainId") + b.Property("LastModified") .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Name") .HasColumnType("TEXT"); - b.Property("PortID") + b.Property("PortId") .HasColumnType("INTEGER"); b.Property("RangeRule") @@ -169,207 +84,174 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("RevisionSelectionStrategy") .HasColumnType("INTEGER"); - b.Property("SpecifiedRevisionId") - .HasColumnType("TEXT"); - b.HasKey("Id"); b.HasIndex("ActiveRevisionId"); - b.HasIndex("ApplicationId"); - - b.HasIndex("ConfigurationId"); - - b.HasIndex("DomainId"); - - b.HasIndex("SpecifiedRevisionId"); + b.HasIndex("AppId"); b.ToTable("Channels"); }); - modelBuilder.Entity("Hippo.Models.Configuration", b => + modelBuilder.Entity("Hippo.Core.Entities.Domain", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.HasKey("Id"); + b.Property("ChannelId") + .HasColumnType("TEXT"); - b.ToTable("Configuration"); - }); + b.Property("Created") + .HasColumnType("TEXT"); - modelBuilder.Entity("Hippo.Models.Domain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("CreatedBy") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModified") + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Name") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("Name") + b.HasIndex("ChannelId") .IsUnique(); b.ToTable("Domains"); }); - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => + modelBuilder.Entity("Hippo.Core.Entities.EnvironmentVariable", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("ConfigurationId") + b.Property("ChannelId") .HasColumnType("TEXT"); b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); b.Property("Key") - .IsRequired() .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedBy") + .HasColumnType("TEXT"); b.Property("Value") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("ConfigurationId"); + b.HasIndex("ChannelId"); b.ToTable("EnvironmentVariables"); }); - modelBuilder.Entity("Hippo.Models.EventLogEntry", b => + modelBuilder.Entity("Hippo.Core.Entities.Revision", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("ApplicationId") - .HasColumnType("TEXT"); - - b.Property("ChannelId") + b.Property("AppId") .HasColumnType("TEXT"); b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); - - b.Property("Description") - .IsRequired() .HasColumnType("TEXT"); - b.Property("EventKind") - .HasColumnType("INTEGER"); - - b.Property("EventSource") - .HasColumnType("INTEGER"); + b.Property("CreatedBy") + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("LastModified") + .HasColumnType("TEXT"); - b.Property("Timestamp") + b.Property("LastModifiedBy") .HasColumnType("TEXT"); - b.Property("UserName") + b.Property("RevisionNumber") .HasColumnType("TEXT"); b.HasKey("Id"); - b.ToTable("EventLogEntries"); + b.HasIndex("AppId"); + + b.ToTable("Revisions"); }); - modelBuilder.Entity("Hippo.Models.Key", b => + modelBuilder.Entity("Hippo.Infrastructure.Identity.Account", b => { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("Id") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); - b.Property("PrivateKey") - .IsRequired() + b.Property("Email") + .HasMaxLength(256) .HasColumnType("TEXT"); - b.Property("PublicKey") - .IsRequired() + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); - b.ToTable("Keys"); - }); + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); - modelBuilder.Entity("Hippo.Models.Revision", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("PasswordHash") .HasColumnType("TEXT"); - b.Property("ApplicationId") + b.Property("PhoneNumber") .HasColumnType("TEXT"); - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); - b.Property("Modified") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("TEXT") - .HasDefaultValueSql("datetime('now')"); + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); - b.Property("RevisionNumber") - .IsRequired() + b.Property("UserName") + .HasMaxLength(256) .HasColumnType("TEXT"); b.HasKey("Id"); - b.HasIndex("ApplicationId"); + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); - b.ToTable("Revisions"); + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -395,7 +277,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AspNetRoles"); + b.ToTable("AspNetRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -418,7 +300,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims"); + b.ToTable("AspNetRoleClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -441,7 +323,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims"); + b.ToTable("AspNetUserClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -463,7 +345,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins"); + b.ToTable("AspNetUserLogins", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -478,7 +360,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles"); + b.ToTable("AspNetUserRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -497,80 +379,57 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Hippo.Models.Account", b => - { - b.HasOne("Hippo.Models.Application", null) - .WithMany("Collaborators") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Key", "SigningKey") - .WithMany() - .HasForeignKey("SigningKeyId"); - - b.Navigation("SigningKey"); + b.ToTable("AspNetUserTokens", (string)null); }); - modelBuilder.Entity("Hippo.Models.Application", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { - b.HasOne("Hippo.Models.Account", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Hippo.Models.Channel", b => - { - b.HasOne("Hippo.Models.Revision", "ActiveRevision") + b.HasOne("Hippo.Core.Entities.Revision", "ActiveRevision") .WithMany() .HasForeignKey("ActiveRevisionId"); - b.HasOne("Hippo.Models.Application", "Application") + b.HasOne("Hippo.Core.Entities.App", "App") .WithMany("Channels") - .HasForeignKey("ApplicationId"); - - b.HasOne("Hippo.Models.Configuration", "Configuration") - .WithMany() - .HasForeignKey("ConfigurationId"); - - b.HasOne("Hippo.Models.Domain", "Domain") - .WithMany() - .HasForeignKey("DomainId"); - - b.HasOne("Hippo.Models.Revision", "SpecifiedRevision") - .WithMany() - .HasForeignKey("SpecifiedRevisionId"); + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.Navigation("ActiveRevision"); - b.Navigation("Application"); - - b.Navigation("Configuration"); + b.Navigation("App"); + }); - b.Navigation("Domain"); + modelBuilder.Entity("Hippo.Core.Entities.Domain", b => + { + b.HasOne("Hippo.Core.Entities.Channel", "Channel") + .WithOne("Domain") + .HasForeignKey("Hippo.Core.Entities.Domain", "ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("SpecifiedRevision"); + b.Navigation("Channel"); }); - modelBuilder.Entity("Hippo.Models.EnvironmentVariable", b => + modelBuilder.Entity("Hippo.Core.Entities.EnvironmentVariable", b => { - b.HasOne("Hippo.Models.Configuration", "Configuration") + b.HasOne("Hippo.Core.Entities.Channel", "Channel") .WithMany("EnvironmentVariables") - .HasForeignKey("ConfigurationId"); + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Configuration"); + b.Navigation("Channel"); }); - modelBuilder.Entity("Hippo.Models.Revision", b => + modelBuilder.Entity("Hippo.Core.Entities.Revision", b => { - b.HasOne("Hippo.Models.Application", "Application") + b.HasOne("Hippo.Core.Entities.App", "App") .WithMany("Revisions") - .HasForeignKey("ApplicationId"); + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Application"); + b.Navigation("App"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -584,7 +443,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -593,7 +452,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -608,7 +467,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -617,24 +476,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Hippo.Models.Account", null) + b.HasOne("Hippo.Infrastructure.Identity.Account", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("Hippo.Models.Application", b => + modelBuilder.Entity("Hippo.Core.Entities.App", b => { b.Navigation("Channels"); - b.Navigation("Collaborators"); - b.Navigation("Revisions"); }); - modelBuilder.Entity("Hippo.Models.Configuration", b => + modelBuilder.Entity("Hippo.Core.Entities.Channel", b => { + b.Navigation("Domain"); + b.Navigation("EnvironmentVariables"); }); #pragma warning restore 612, 618 diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Infrastructure/DependencyInjection.cs new file mode 100644 index 000000000..f6b6533ff --- /dev/null +++ b/src/Infrastructure/DependencyInjection.cs @@ -0,0 +1,79 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Infrastructure.Data; +using Hippo.Infrastructure.Exceptions; +using Hippo.Infrastructure.Files; +using Hippo.Infrastructure.Identity; +using Hippo.Infrastructure.JobSchedulers; +using Hippo.Infrastructure.ReverseProxies; +using Hippo.Infrastructure.ReverseProxies.Configuration; +using Hippo.Infrastructure.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Yarp.ReverseProxy.Configuration; + +namespace Hippo.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + // NOTE(bacongobbler): to create a new migration, run `dotnet ef migrations add "SampleMigration" --project src/Infrastructure --startup-project src/Web --output-dir Data/Migrations` + var driver = configuration.GetValue("Database:Driver", "inmemory").ToLower(); + switch (driver) + { + case "inmemory": + services.AddDbContext(options => + options.UseInMemoryDatabase("Hippo")); + break; + case "postgresql": + services.AddDbContext(options => + options.UseNpgsql( + configuration.GetConnectionString("Database"), + b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); + break; + case "sqlite": + services.AddDbContext(options => + options.UseSqlite( + configuration.GetConnectionString("Database"), + b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); + break; + default: + throw new InvalidDatabaseDriverException(driver); + } + + services.AddScoped(provider => provider.GetRequiredService()); + + services.AddScoped(); + + services.AddIdentity() + .AddRoles() + .AddEntityFrameworkStores(); + + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + + services.AddTransient(); + + services.AddSingleton(); + + services.AddTransient(); + + services.AddAuthorization(options => + options.AddPolicy("CanPurge", policy => policy.RequireRole("Administrator"))); + + if (configuration.GetValue("ReverseProxy:Enabled")) + { + // YarpReverseProxy and YARP itself need to share a config provider + InMemoryConfigProvider configProvider = new InMemoryConfigProvider(); + services.AddSingleton(new YarpReverseProxy(configProvider)); + services.AddReverseProxy().Services.AddSingleton(configProvider); + } + + return services; + } +} diff --git a/src/Infrastructure/Exceptions/InvalidDatabaseDriverException.cs b/src/Infrastructure/Exceptions/InvalidDatabaseDriverException.cs new file mode 100644 index 000000000..e6eb5b9ba --- /dev/null +++ b/src/Infrastructure/Exceptions/InvalidDatabaseDriverException.cs @@ -0,0 +1,9 @@ +namespace Hippo.Infrastructure.Exceptions; + +public class InvalidDatabaseDriverException : Exception +{ + public InvalidDatabaseDriverException(string driver) + : base($"Database driver \"{driver}\" is invalid.") + { + } +} diff --git a/src/Infrastructure/Files/JsonFileBuilder.cs b/src/Infrastructure/Files/JsonFileBuilder.cs new file mode 100644 index 000000000..0964b9f87 --- /dev/null +++ b/src/Infrastructure/Files/JsonFileBuilder.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using Hippo.Application.Apps.Queries; +using Hippo.Application.Channels.Queries; +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Domains.Queries; +using Hippo.Application.EnvironmentVariables.Queries; +using Hippo.Application.Revisions.Queries; + +namespace Hippo.Infrastructure.Files; + +public class JsonFileBuilder : IJsonFileBuilder +{ + public byte[] BuildAppsFile(IEnumerable records) + { + using var memoryStream = new MemoryStream(); + + using (var streamWriter = new StreamWriter(memoryStream)) + { + string jsonString = JsonSerializer.Serialize(records); + streamWriter.WriteLine(jsonString); + } + + return memoryStream.ToArray(); + } + + public byte[] BuildChannelsFile(IEnumerable records) + { + using var memoryStream = new MemoryStream(); + + using (var streamWriter = new StreamWriter(memoryStream)) + { + string jsonString = JsonSerializer.Serialize(records); + streamWriter.WriteLine(jsonString); + } + + return memoryStream.ToArray(); + } + + public byte[] BuildDomainsFile(IEnumerable records) + { + using var memoryStream = new MemoryStream(); + + using (var streamWriter = new StreamWriter(memoryStream)) + { + string jsonString = JsonSerializer.Serialize(records); + streamWriter.WriteLine(jsonString); + } + + return memoryStream.ToArray(); + } + + public byte[] BuildEnvironmentVariablesFile(IEnumerable records) + { + using var memoryStream = new MemoryStream(); + + using (var streamWriter = new StreamWriter(memoryStream)) + { + string jsonString = JsonSerializer.Serialize(records); + streamWriter.WriteLine(jsonString); + } + + return memoryStream.ToArray(); + } + + public byte[] BuildRevisionsFile(IEnumerable records) + { + using var memoryStream = new MemoryStream(); + + using (var streamWriter = new StreamWriter(memoryStream)) + { + string jsonString = JsonSerializer.Serialize(records); + streamWriter.WriteLine(jsonString); + } + + return memoryStream.ToArray(); + } +} diff --git a/src/Infrastructure/Identity/Account.cs b/src/Infrastructure/Identity/Account.cs new file mode 100644 index 000000000..ecc19f4f0 --- /dev/null +++ b/src/Infrastructure/Identity/Account.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Identity; + +namespace Hippo.Infrastructure.Identity; + +public class Account : IdentityUser +{ +} diff --git a/src/Infrastructure/Identity/IdentityResultExtensions.cs b/src/Infrastructure/Identity/IdentityResultExtensions.cs new file mode 100644 index 000000000..2dd7df0fc --- /dev/null +++ b/src/Infrastructure/Identity/IdentityResultExtensions.cs @@ -0,0 +1,14 @@ +using Hippo.Application.Common.Models; +using Microsoft.AspNetCore.Identity; + +namespace Hippo.Infrastructure.Identity; + +public static class IdentityResultExtensions +{ + public static Result ToApplicationResult(this IdentityResult result) + { + return result.Succeeded + ? Result.Success() + : Result.Failure(result.Errors.Select(e => e.Description)); + } +} diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs new file mode 100644 index 000000000..07425418d --- /dev/null +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -0,0 +1,105 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace Hippo.Infrastructure.Identity; + +public class IdentityService : IIdentityService +{ + private readonly UserManager _userManager; + + private readonly IUserClaimsPrincipalFactory _userClaimsPrincipalFactory; + + private readonly IAuthorizationService _authorizationService; + + public IdentityService( + UserManager userManager, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory, + IAuthorizationService authorizationService) + { + _userManager = userManager; + _userClaimsPrincipalFactory = userClaimsPrincipalFactory; + _authorizationService = authorizationService; + } + + public async Task GetUserNameAsync(string userId) + { + var user = await _userManager.Users.FirstAsync(u => u.Id == userId); + return user.UserName; + } + + public async Task GetUserIdAsync(string userName) + { + var user = await _userManager.Users.FirstAsync(u => u.UserName == userName); + return user.Id; + } + + public async Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password) + { + var user = new Account + { + UserName = userName, + Email = userName + }; + + var result = await _userManager.CreateAsync(user, password); + + return (result.ToApplicationResult(), user.Id); + } + + public async Task IsInRoleAsync(string userId, string role) + { + var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); + + return user != null && await _userManager.IsInRoleAsync(user, role); + } + + public async Task AuthorizeAsync(string userId, string policyName) + { + var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); + + if (user == null) + { + return false; + } + + var principal = await _userClaimsPrincipalFactory.CreateAsync(user); + + var result = await _authorizationService.AuthorizeAsync(principal, policyName); + + return result.Succeeded; + } + + public async Task DeleteUserAsync(string userId) + { + var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); + + return user != null ? await DeleteUserAsync(user) : Result.Success(); + } + + public async Task DeleteUserAsync(Account user) + { + var result = await _userManager.DeleteAsync(user); + + return result.ToApplicationResult(); + } + + public async Task CheckPasswordAsync(string userName, string password) + { + var user = _userManager.Users.SingleOrDefault(u => u.UserName == userName); + + if (user == null) + { + return false; + } + + return await _userManager.CheckPasswordAsync(user, password); + } + + public async Task GetUserNamesAsync() + { + return await _userManager.Users.Select(a => a.UserName).ToArrayAsync(); + } +} diff --git a/src/Infrastructure/Identity/SignInResultExtensions.cs b/src/Infrastructure/Identity/SignInResultExtensions.cs new file mode 100644 index 000000000..ea5625698 --- /dev/null +++ b/src/Infrastructure/Identity/SignInResultExtensions.cs @@ -0,0 +1,34 @@ +using Hippo.Application.Common.Models; +using Microsoft.AspNetCore.Identity; + +namespace Hippo.Infrastructure.Identity; + +public static class SignInResultExtensions +{ + public static Result ToApplicationResult(this SignInResult result) + { + if (result.Succeeded) + { + return Result.Success(); + } + + var errors = new string[] { }; + + if (result.IsLockedOut) + { + errors.Append("Account locked."); + } + + if (result.IsNotAllowed) + { + errors.Append("Account not allowed to sign in."); + } + + if (result.RequiresTwoFactor) + { + errors.Append("Account requires two-factor authentication to log in."); + } + + return Result.Failure(errors); + } +} diff --git a/src/Infrastructure/Identity/SignInService.cs b/src/Infrastructure/Identity/SignInService.cs new file mode 100644 index 000000000..73e3da8bc --- /dev/null +++ b/src/Infrastructure/Identity/SignInService.cs @@ -0,0 +1,39 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using MediatR; +using Microsoft.AspNetCore.Identity; + +namespace Hippo.Infrastructure.Identity; + +public class SignInService : ISignInService +{ + private readonly SignInManager _signInManager; + + private readonly UserManager _userManager; + + public SignInService(SignInManager signInManager, UserManager userManager) + { + _signInManager = signInManager; + _userManager = userManager; + } + + public async Task PasswordSignInAsync(string username, string password, bool rememberMe = false) + { + var user = _userManager.Users.SingleOrDefault(u => u.UserName == username); + + if (user == null) + { + return Result.Failure(new string[] { "Account does not exist." }); + } + + var result = await _signInManager.PasswordSignInAsync(user, password, rememberMe, false); + + return result.ToApplicationResult(); + } + + public async Task SignOutAsync() + { + await _signInManager.SignOutAsync(); + return Unit.Value; + } +} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj new file mode 100644 index 000000000..17a7c8d4d --- /dev/null +++ b/src/Infrastructure/Infrastructure.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + Hippo.Infrastructure + Hippo.Infrastructure + + + + + + + + + + + + + + + + + + diff --git a/src/Hippo/Schedulers/WagiLocalJobScheduler.cs b/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs similarity index 70% rename from src/Hippo/Schedulers/WagiLocalJobScheduler.cs rename to src/Infrastructure/JobSchedulers/LocalJobScheduler.cs index 34cc06bfc..8b7e49ff4 100644 --- a/src/Hippo/Schedulers/WagiLocalJobScheduler.cs +++ b/src/Infrastructure/JobSchedulers/LocalJobScheduler.cs @@ -1,25 +1,41 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Hippo.Models; +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Common; +using Hippo.Core.Entities; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Hippo.Schedulers; +namespace Hippo.Infrastructure.JobSchedulers; -public class WagiLocalJobScheduler : JobScheduler +public class LocalJobScheduler : IJobScheduler, IHasDomainEvent { + public const int EphemeralPortStartRange = 32768; + + private readonly ILogger _logger; + + private readonly IConfiguration _configuration; + + private readonly string _bindleUrl; + + public List DomainEvents { get; set; } = new List(); + // This assumes a singleton scheduler instance! private readonly Dictionary _wagiProcessIds = new(); - private const string ENV_WAGI = "HIPPO_WAGI_PATH"; - public WagiLocalJobScheduler(IHostApplicationLifetime lifetime, ILogger logger, IHostEnvironment env) - : base(logger, env) + public LocalJobScheduler(ILogger logger, IConfiguration configuration, IHostApplicationLifetime lifetime) { + _logger = logger; + + _configuration = configuration; + + _bindleUrl = _configuration.GetValue("Bindle:Url", "http://127.0.0.1:8080/v1"); + if (_bindleUrl == null) + { + throw new ArgumentException("Bindle URL cannot be null."); + } + lifetime.ApplicationStopping.Register(() => { foreach (var processId in _wagiProcessIds) @@ -31,24 +47,24 @@ public WagiLocalJobScheduler(IHostApplicationLifetime lifetime, ILogger $"--env {ev.Key}=\"{ev.Value}\"")); + var env = String.Join(' ', c.EnvironmentVariables.Select(ev => $"--env {ev.Key}=\"{ev.Value}\"")); var psi = new ProcessStartInfo { FileName = wagiProgram, - Arguments = $"-b {c.Application.StorageId}/{c.ActiveRevision.RevisionNumber} --bindle-url {_bindleUrl} -l 127.0.0.1:{port} {env}", + Arguments = $"-b {c.App.StorageId}/{c.ActiveRevision.RevisionNumber} --bindle-url {_bindleUrl} -l 127.0.0.1:{port} {env}", RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false, @@ -61,6 +77,13 @@ public override void Start(Channel c) { using (var process = Process.Start(psi)) { + if (process == null) + { + // TODO: probably want to throw an Exception here instead + _logger.LogError($"Process {psi.FileName} with arguments {psi.Arguments} never started"); + return; + } + process.EnableRaisingEvents = true; // TODO: this event handler does not always fire, if the program immediately exits (for example because the command line is wrong because an old version // of wagi is being used then the process object may go out of scope before the event handler fires. @@ -68,12 +91,11 @@ public override void Start(Channel c) process.Exited += (s, e) => { _wagiProcessIds.Remove(c.Id); - OnChannelStopped(c); }; process.Start(); var log = Task.WhenAll( - ForwardLogs(process.StandardOutput, $"{c.Application.Name}:{c.Name}:wagi:stdout", LogLevel.Trace), - ForwardLogs(process.StandardError, $"{c.Application.Name}:{c.Name}:wagi:stderr") + ForwardLogs(process.StandardOutput, $"{c.App.Name}:{c.Name}:wagi:stdout", LogLevel.Trace), + ForwardLogs(process.StandardError, $"{c.App.Name}:{c.Name}:wagi:stderr") ); if (process.HasExited) { @@ -81,10 +103,6 @@ public override void Start(Channel c) } else { - var data = new ChannelStartedEventArgs(); - data.Channel = c; - data.ListenAddress = $"http://{listenAddress}"; - OnChannelStarted(data); _wagiProcessIds[c.Id] = (process.Id, log); } @@ -94,14 +112,13 @@ public override void Start(Channel c) { if (e.Message.Contains("No such file or directory", StringComparison.InvariantCultureIgnoreCase) || e.Message.Contains("The system cannot find the file specified", StringComparison.InvariantCultureIgnoreCase)) { - _logger.LogError($"Program '{wagiProgram}' not found: check system path or set {ENV_WAGI}"); - return; + throw new ArgumentException($"The system cannot find '{wagiProgram}'; add '{wagiProgram}' to your $PATH or set 'Wagi:BinaryPath' in your appsettings to the correct location."); } throw; } } - public override void Stop(Channel c) + public void Stop(Channel c) { if (_wagiProcessIds.TryGetValue(c.Id, out var wagiProcess)) { @@ -110,15 +127,13 @@ public override void Stop(Channel c) var (wagiProcessId, log) = wagiProcess; KillProcessById(wagiProcessId); log.GetAwaiter().GetResult(); - OnChannelStopped(c); } } // TODO: deduplicate without incurring a check on every line - private async Task ForwardLogs(StreamReader source, string streamId) { - string line; + string? line; while ((line = await source.ReadLineAsync()) != null) { _logger.Log(ConvertLogLevel(line), $"{streamId}: {line}"); @@ -127,7 +142,7 @@ private async Task ForwardLogs(StreamReader source, string streamId) private async Task ForwardLogs(StreamReader source, string streamId, LogLevel logLevel) { - string line; + string? line; while ((line = await source.ReadLineAsync()) != null) { _logger.Log(logLevel, $"{streamId}: {line}"); @@ -152,16 +167,15 @@ private static LogLevel ConvertLogLevel(string rustTraceLine) }; } - private static string ExtractRustTraceLevel(string rustTraceLine) => + private static string? ExtractRustTraceLevel(string rustTraceLine) => rustTraceLine.Split(' ').Select(AsRustTraceLevel).FirstOrDefault(s => s != null); - private static string AsRustTraceLevel(string fragment) => + private static string? AsRustTraceLevel(string fragment) => RUST_TRACE_LEVELS.Where(level => fragment.Contains(level, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - private static string WagiBinaryPath() + private string WagiBinaryPath() { - return Environment.GetEnvironmentVariable(ENV_WAGI) ?? - (OperatingSystem.IsWindows() ? "wagi.exe" : "wagi"); + return _configuration.GetValue("Wagi:BinaryPath", (OperatingSystem.IsWindows() ? "wagi.exe" : "wagi")); } private static void KillProcessById(int wagiProcessId) diff --git a/src/Infrastructure/ReverseProxies/Configuration/InMemoryConfigProvider.cs b/src/Infrastructure/ReverseProxies/Configuration/InMemoryConfigProvider.cs new file mode 100644 index 000000000..a6564b20d --- /dev/null +++ b/src/Infrastructure/ReverseProxies/Configuration/InMemoryConfigProvider.cs @@ -0,0 +1,83 @@ +using Hippo.Application.Common.Exceptions; +using Microsoft.Extensions.Primitives; +using Yarp.ReverseProxy.Configuration; + +namespace Hippo.Infrastructure.ReverseProxies.Configuration; + +/// +public class InMemoryConfigProvider : IProxyConfigProvider +{ + private List _routes = new List(); + + private List _clusters = new List(); + + private volatile InMemoryConfig _config = new InMemoryConfig(new List(), new List()); + + public IProxyConfig GetConfig() => _config; + + public void AddRoute(RouteConfig routeConfig) + { + _routes.Add(routeConfig); + Update(); + } + + public void AddCluster(ClusterConfig clusterConfig) + { + _clusters.Add(clusterConfig); + Update(); + } + + public void RemoveRoute(string id) + { + int index = _routes.FindIndex(r => r.RouteId == id); + if (index == -1) + { + throw new NotFoundException(nameof(RouteConfig), id); + } + _routes.RemoveAt(index); + Update(); + } + + public void RemoveCluster(string id) + { + int index = _clusters.FindIndex(c => c.ClusterId == id); + if (index == -1) + { + throw new NotFoundException(nameof(ClusterConfig), id); + } + _clusters.RemoveAt(index); + Update(); + } + + private void Update() + { + var oldConfig = _config; + _config = new InMemoryConfig(_routes, _clusters); + oldConfig.SignalChange(); + } + + private class InMemoryConfig : IProxyConfig + { + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + + public InMemoryConfig(IReadOnlyList routes, IReadOnlyList clusters) + { + Routes = routes; + Clusters = clusters; + ChangeToken = new CancellationChangeToken(_cts.Token); + } + + public IReadOnlyList Routes { get; } + + public IReadOnlyList Clusters { get; } + + public IChangeToken ChangeToken { get; } + + internal void SignalChange() + { + _cts.Cancel(); + } + } +} diff --git a/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs b/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs new file mode 100644 index 000000000..bda9e098d --- /dev/null +++ b/src/Infrastructure/ReverseProxies/YarpReverseProxy.cs @@ -0,0 +1,63 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Core.Entities; +using Hippo.Infrastructure.ReverseProxies.Configuration; +using Yarp.ReverseProxy.Configuration; + +namespace Hippo.Infrastructure.ReverseProxies; + +public class YarpReverseProxy : IReverseProxy +{ + private readonly InMemoryConfigProvider _configProvider; + + public YarpReverseProxy(InMemoryConfigProvider configProvider) + { + _configProvider = configProvider; + } + + public void Start(Channel c, string address) + { + if (c.Domain != null && c.Domain.Name != null) + { + var key = GetKey(c.AppId, c.Id); + + var routeConfig = new RouteConfig() + { + RouteId = key, + ClusterId = key, + Match = new RouteMatch + { + Hosts = new List() + { + c.Domain.Name + } + } + }; + + var clusterConfig = new ClusterConfig() + { + ClusterId = key, + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { + key, new DestinationConfig() + { + Address = address + } + } + } + }; + + _configProvider.AddCluster(clusterConfig); + _configProvider.AddRoute(routeConfig); + } + } + + public void Stop(Channel c) + { + var key = GetKey(c.AppId, c.Id); + _configProvider.RemoveRoute(key); + _configProvider.RemoveCluster(key); + } + + private static string GetKey(Guid appId, Guid channelId) => $"App:{appId}-Channel:{channelId}"; +} diff --git a/src/Infrastructure/Services/DateTimeService.cs b/src/Infrastructure/Services/DateTimeService.cs new file mode 100644 index 000000000..ab8c81225 --- /dev/null +++ b/src/Infrastructure/Services/DateTimeService.cs @@ -0,0 +1,10 @@ +using Hippo.Application.Common.Interfaces; + +namespace Hippo.Infrastructure.Services; + +public class DateTimeService : IDateTime +{ + public DateTime Now => DateTime.Now; + + public DateTime UtcNow => DateTime.UtcNow; +} diff --git a/src/Infrastructure/Services/DomainEventService.cs b/src/Infrastructure/Services/DomainEventService.cs new file mode 100644 index 000000000..548d200a5 --- /dev/null +++ b/src/Infrastructure/Services/DomainEventService.cs @@ -0,0 +1,32 @@ +using Hippo.Application.Common.Interfaces; +using Hippo.Application.Common.Models; +using Hippo.Core.Common; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Hippo.Infrastructure.Services; + +public class DomainEventService : IDomainEventService +{ + private readonly ILogger _logger; + + private readonly IPublisher _mediator; + + public DomainEventService(ILogger logger, IPublisher mediator) + { + _logger = logger; + _mediator = mediator; + } + + public async Task Publish(DomainEvent domainEvent) + { + _logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name); + await _mediator.Publish(GetNotificationCorrespondingToDomainEvent(domainEvent)); + } + + private INotification GetNotificationCorrespondingToDomainEvent(DomainEvent domainEvent) + { + return (INotification)Activator.CreateInstance( + typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType()), domainEvent)!; + } +} diff --git a/src/Infrastructure/Services/TokenService.cs b/src/Infrastructure/Services/TokenService.cs new file mode 100644 index 000000000..aa860a47b --- /dev/null +++ b/src/Infrastructure/Services/TokenService.cs @@ -0,0 +1,40 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Hippo.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; + +namespace Hippo.Infrastructure.Services; + +public class TokenService : ITokenService +{ + private readonly IConfiguration _configuration; + + public TokenService(IConfiguration configuration) + { + _configuration = configuration; + } + + public TokenInfo CreateSecurityToken(string id) + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, id), + // jti - unique string that is representative of each token so using a guid + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + + var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); + + var token = new JwtSecurityToken( + issuer: _configuration["Jwt:Issuer"], + audience: _configuration["Jwt:Audience"], + expires: DateTime.Now.AddDays(90), + claims: claims, + signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256) + ); + + return new TokenInfo(new JwtSecurityTokenHandler().WriteToken(token), token.ValidTo); + } +} diff --git a/src/Hippo/Tasks/TaskQueue.cs b/src/Infrastructure/Tasks/TaskQueue.cs similarity index 74% rename from src/Hippo/Tasks/TaskQueue.cs rename to src/Infrastructure/Tasks/TaskQueue.cs index 3ca42e3e0..b2a3ce154 100644 --- a/src/Hippo/Tasks/TaskQueue.cs +++ b/src/Infrastructure/Tasks/TaskQueue.cs @@ -1,15 +1,7 @@ -using System.Threading; using System.Threading.Channels; -using System.Threading.Tasks; +using Hippo.Application.Common.Interfaces; -namespace Hippo.Tasks; - -public interface ITaskQueue -{ - Task Enqueue(T value, CancellationToken cancellationToken); - Task Dequeue(CancellationToken cancellationToken); - (bool, T) TryRead(); -} +namespace Hippo.Infrastructure.Tasks; public class TaskQueue : ITaskQueue { @@ -35,7 +27,7 @@ public async Task Dequeue(CancellationToken cancellationToken) return await _queue.Reader.ReadAsync(cancellationToken); } - public (bool, T) TryRead() + public (bool, T?) TryRead() { var result = _queue.Reader.TryRead(out var item); return (result, item); diff --git a/src/Hippo/.vscode/launch.json b/src/Web/.vscode/launch.json similarity index 100% rename from src/Hippo/.vscode/launch.json rename to src/Web/.vscode/launch.json diff --git a/src/Hippo/.vscode/tasks.json b/src/Web/.vscode/tasks.json similarity index 99% rename from src/Hippo/.vscode/tasks.json rename to src/Web/.vscode/tasks.json index 9745b70d1..0918ef0be 100644 --- a/src/Hippo/.vscode/tasks.json +++ b/src/Web/.vscode/tasks.json @@ -39,4 +39,4 @@ "problemMatcher": "$msCompile" } ] -} \ No newline at end of file +} diff --git a/src/Web/Api/AccountController.cs b/src/Web/Api/AccountController.cs new file mode 100644 index 000000000..3e8685ba0 --- /dev/null +++ b/src/Web/Api/AccountController.cs @@ -0,0 +1,22 @@ +using Hippo.Application.Accounts.Commands; +using Hippo.Application.Common.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +public class AccountController : ApiControllerBase +{ + [HttpPost] + public async Task> Register(CreateAccountCommand command) + { + return await Mediator.Send(command); + } + + [HttpPost] + public async Task> CreateToken(CreateTokenCommand command) + { + return await Mediator.Send(command); + } +} diff --git a/src/Web/Api/ApiControllerBase.cs b/src/Web/Api/ApiControllerBase.cs new file mode 100644 index 000000000..5f798d697 --- /dev/null +++ b/src/Web/Api/ApiControllerBase.cs @@ -0,0 +1,11 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +public abstract class ApiControllerBase : Controller +{ + private ISender _mediator = null!; + + protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService(); +} diff --git a/src/Web/Api/AppController.cs b/src/Web/Api/AppController.cs new file mode 100644 index 000000000..641fe61aa --- /dev/null +++ b/src/Web/Api/AppController.cs @@ -0,0 +1,38 @@ +using Hippo.Application.Apps.Commands; +using Hippo.Application.Apps.Queries; +using Hippo.Application.Common.Security; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +[Authorize] +public class AppController : ApiControllerBase +{ + public async Task> Index() + { + return await Mediator.Send(new GetAppsQuery()); + } + + public async Task Export() + { + var vm = await Mediator.Send(new ExportAppsQuery()); + + return File(vm.Content, vm.ContentType, vm.FileName); + } + + [HttpPost] + public async Task> Create(CreateAppCommand command) + { + return await Mediator.Send(command); + } + + [HttpDelete] + public async Task Delete(DeleteAppCommand command) + { + await Mediator.Send(command); + + return NoContent(); + } +} diff --git a/src/Web/Api/ChannelController.cs b/src/Web/Api/ChannelController.cs new file mode 100644 index 000000000..bd778d2b0 --- /dev/null +++ b/src/Web/Api/ChannelController.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Channels.Commands; +using Hippo.Application.Channels.Queries; +using Hippo.Application.Common.Security; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +[Authorize] +public class ChannelController : ApiControllerBase +{ + [HttpGet] + public async Task> Index() + { + return await Mediator.Send(new GetChannelsQuery()); + } + + [HttpGet] + public async Task Index(Guid id) + { + var vm = await Mediator.Send(new ExportChannelsQuery()); + + return File(vm.Content, vm.ContentType, vm.FileName); + } + + [HttpPost] + public async Task> Create(CreateChannelCommand command) + { + return await Mediator.Send(command); + } + + [HttpDelete] + public async Task Delete(Guid id) + { + await Mediator.Send(new DeleteChannelCommand { Id = id }); + + return NoContent(); + } +} diff --git a/src/Web/Api/DomainController.cs b/src/Web/Api/DomainController.cs new file mode 100644 index 000000000..ad3bff174 --- /dev/null +++ b/src/Web/Api/DomainController.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Security; +using Hippo.Application.Domains.Commands; +using Hippo.Application.Domains.Queries; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +[Authorize] +public class DomainController : ApiControllerBase +{ + [HttpGet] + public async Task> Index() + { + return await Mediator.Send(new GetDomainsQuery()); + } + + [HttpGet] + public async Task Index(Guid id) + { + var vm = await Mediator.Send(new ExportDomainsQuery()); + + return File(vm.Content, vm.ContentType, vm.FileName); + } + + [HttpPost] + public async Task> Create(CreateDomainCommand command) + { + return await Mediator.Send(command); + } + + [HttpDelete] + public async Task Delete(Guid id) + { + await Mediator.Send(new DeleteDomainCommand { Id = id }); + + return NoContent(); + } +} diff --git a/src/Web/Api/EnvironmentVariableController.cs b/src/Web/Api/EnvironmentVariableController.cs new file mode 100644 index 000000000..90a2bf3bd --- /dev/null +++ b/src/Web/Api/EnvironmentVariableController.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Security; +using Hippo.Application.EnvironmentVariables.Commands; +using Hippo.Application.EnvironmentVariables.Queries; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +[Authorize] +public class EnvironmentVariableController : ApiControllerBase +{ + [HttpGet] + public async Task> Index() + { + return await Mediator.Send(new GetEnvironmentVariablesQuery()); + } + + [HttpGet] + public async Task Index(Guid id) + { + var vm = await Mediator.Send(new ExportEnvironmentVariablesQuery()); + + return File(vm.Content, vm.ContentType, vm.FileName); + } + + [HttpPost] + public async Task> Create(CreateEnvironmentVariableCommand command) + { + return await Mediator.Send(command); + } + + [HttpDelete] + public async Task Delete(Guid id) + { + await Mediator.Send(new DeleteEnvironmentVariableCommand { Id = id }); + + return NoContent(); + } +} diff --git a/src/Web/Api/RevisionController.cs b/src/Web/Api/RevisionController.cs new file mode 100644 index 000000000..7246e88b3 --- /dev/null +++ b/src/Web/Api/RevisionController.cs @@ -0,0 +1,40 @@ +using Hippo.Application.Common.Security; +using Hippo.Application.Revisions.Commands; +using Hippo.Application.Revisions.Queries; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Api; + +[Route("api/[controller]", Name = "Api[controller]")] +[ApiController] +[Authorize] +public class RevisionController : ApiControllerBase +{ + [HttpGet] + public async Task> Index() + { + return await Mediator.Send(new GetRevisionsQuery()); + } + + [HttpGet] + public async Task Index(Guid id) + { + var vm = await Mediator.Send(new ExportRevisionsQuery()); + + return File(vm.Content, vm.ContentType, vm.FileName); + } + + [HttpPost] + public async Task> Create(CreateRevisionCommand command) + { + return await Mediator.Send(command); + } + + [HttpDelete] + public async Task Delete(Guid id) + { + await Mediator.Send(new DeleteRevisionCommand { Id = id }); + + return NoContent(); + } +} diff --git a/src/Web/Controllers/AccountController.cs b/src/Web/Controllers/AccountController.cs new file mode 100644 index 000000000..eefea9f00 --- /dev/null +++ b/src/Web/Controllers/AccountController.cs @@ -0,0 +1,64 @@ +using Hippo.Application.Accounts.Commands; +using Hippo.Application.Common.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Controllers; + +public class AccountController : WebUIControllerBase +{ + [HttpGet] + public ActionResult Register() + { + return View(new CreateAccountCommand()); + } + + [HttpPost] + public async Task> Register(CreateAccountCommand command) + { + try + { + await Mediator.Send(command); + return RedirectToAction(nameof(Login)); + } + catch (Exception e) + { + ModelState.AddModelError("", e.Message); + return View(command); + } + } + + [HttpGet] + public IActionResult Login() + { + return View(new LoginAccountCommand()); + } + + [HttpPost] + public async Task Login(LoginAccountCommand command) + { + try + { + await Mediator.Send(command); + return RedirectToAction("Index", "App"); + } + catch (Exception e) + { + ModelState.AddModelError("", e.Message); + return View(command); + } + } + + [HttpGet] + [HttpPost] + public async Task Logout(LogoutAccountCommand command) + { + await Mediator.Send(command); + return RedirectToAction(nameof(Login)); + } + + [HttpPost] + public async Task> CreateToken([FromBody] CreateTokenCommand command) + { + return await Mediator.Send(command); + } +} diff --git a/src/Web/Controllers/AppController.cs b/src/Web/Controllers/AppController.cs new file mode 100644 index 000000000..80e4d2b0c --- /dev/null +++ b/src/Web/Controllers/AppController.cs @@ -0,0 +1,136 @@ +using Hippo.Application.Apps.Commands; +using Hippo.Application.Apps.Queries; +using Hippo.Application.Channels.Commands; +using Hippo.Application.Common.Exceptions; +using Hippo.Application.Revisions.Commands; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Controllers; + +[Authorize] +public class AppController : WebUIControllerBase +{ + [HttpGet] + public async Task> Index() + { + AppsVm vm = await Mediator.Send(new GetAppsQuery()); + + return View(vm); + } + + [HttpGet] + public async Task Details(Guid id) + { + try + { + AppDto dto = await Mediator.Send(new GetAppQuery { Id = id }); + + return View(dto); + } + catch (NotFoundException) + { + return NotFound(); + } + } + + [HttpGet] + public IActionResult Edit(Guid id) + { + try + { + return View(new GetAppQuery { Id = id }); + } + catch (NotFoundException) + { + return NotFound(); + } + } + + [HttpPost] + public async Task Edit(UpdateAppCommand command) + { + try + { + await Mediator.Send(command); + + return RedirectToAction(nameof(Index)); + } + catch (NotFoundException) + { + return NotFound(); + } + } + + [HttpGet] + public IActionResult New() + { + return View(new CreateAppCommand()); + } + + [HttpPost] + public async Task> New(CreateAppCommand command) + { + // TODO: handle validation errors + var id = await Mediator.Send(command); + return RedirectToAction(nameof(Details), new { id = id }); + } + + [HttpGet] + public async Task Delete(Guid id) + { + try + { + AppDto dto = await Mediator.Send(new GetAppQuery { Id = id }); + + return View(dto); + } + catch (NotFoundException) + { + return NotFound(); + } + } + + [HttpDelete] + public async Task Delete(DeleteAppCommand command) + { + try + { + await Mediator.Send(command); + + return RedirectToAction(nameof(Index)); + } + catch (NotFoundException) + { + return NotFound(); + } + } + + [HttpGet] + public IActionResult NewChannel(Guid id) + { + return View(new CreateChannelCommand { AppId = id }); + } + + [HttpPost] + public async Task> NewChannel(CreateChannelCommand command) + { + // TODO: handle validation errors + await Mediator.Send(command); + return RedirectToAction(nameof(Details), new { id = command.AppId }); + } + + [HttpGet] + public IActionResult RegisterRevision(Guid id) + { + return View(new CreateRevisionCommand { AppId = id }); + } + + [HttpPost] + public async Task> RegisterRevision(CreateRevisionCommand command) + { + // TODO: handle validation errors + await Mediator.Send(command); + return RedirectToAction(nameof(Details), new { id = command.AppId }); + } +} diff --git a/src/Hippo/Controllers/StyleguideController.cs b/src/Web/Controllers/StyleguideController.cs similarity index 57% rename from src/Hippo/Controllers/StyleguideController.cs rename to src/Web/Controllers/StyleguideController.cs index d3fa31787..1d0d1e79c 100644 --- a/src/Hippo/Controllers/StyleguideController.cs +++ b/src/Web/Controllers/StyleguideController.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Mvc; -namespace Hippo.Controllers; +namespace Hippo.Web.Controllers; -public class StyleguideController : Controller +public class StyleguideController : WebUIControllerBase { [HttpGet] public IActionResult Index() diff --git a/src/Web/Controllers/WebUIControllerBase.cs b/src/Web/Controllers/WebUIControllerBase.cs new file mode 100644 index 000000000..2be8ad3e4 --- /dev/null +++ b/src/Web/Controllers/WebUIControllerBase.cs @@ -0,0 +1,11 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace Hippo.Web.Controllers; + +public abstract class WebUIControllerBase : Controller +{ + private ISender _mediator = null!; + + protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService(); +} diff --git a/src/Web/Program.cs b/src/Web/Program.cs new file mode 100644 index 000000000..18f41865c --- /dev/null +++ b/src/Web/Program.cs @@ -0,0 +1,100 @@ +using FluentValidation.AspNetCore; +using Hippo.Application; +using Hippo.Application.Common.Interfaces; +using Hippo.Infrastructure; +using Hippo.Infrastructure.Data; +using Hippo.Infrastructure.Identity; +using Hippo.Web.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); + +builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + +builder.Services.AddSingleton(); + +builder.Services.AddHttpContextAccessor(); + +builder.Services.AddHealthChecks() + .AddDbContextCheck(); + +builder.Services.AddControllersWithViews().AddFluentValidation(); + +builder.Services.AddRouting(options => options.LowercaseUrls = true); + +builder.Services.Configure(options => + options.SuppressModelStateInvalidFilter = true); + +builder.WebHost.UseKestrel(options => +{ + options.ListenAnyIP(builder.Configuration.GetValue("Kestrel:Endpoints:Http:Port", 5308)); + options.ListenAnyIP( + builder.Configuration.GetValue("Kestrel:Endpoints:Https:Port", 5309), + listenOptions => listenOptions.UseHttps()); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.UseEndpoints(endpoints => +{ + endpoints.MapControllers(); + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=App}/{action=Index}/{id?}"); + if (builder.Configuration.GetValue("ReverseProxy:Enabled")) + { + endpoints.MapReverseProxy(); + } +}); + +using (var scope = app.Services.CreateScope()) +{ + var services = scope.ServiceProvider; + + try + { + var context = services.GetRequiredService(); + + var databaseDriver = builder.Configuration.GetValue("Database:Driver"); + if (databaseDriver != "inmemory") + { + context.Database.Migrate(); + } + + var userManager = services.GetRequiredService>(); + var roleManager = services.GetRequiredService>(); + + await ApplicationDbContextSeed.SeedIdentityRolesAsync(userManager, roleManager); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + + logger.LogError(ex, "An error occurred while migrating or seeding the database."); + + throw; + } +} + +app.Run(); diff --git a/src/Hippo/Properties/launchSettings.json b/src/Web/Properties/launchSettings.json similarity index 90% rename from src/Hippo/Properties/launchSettings.json rename to src/Web/Properties/launchSettings.json index 8d204b982..fc87c1e01 100644 --- a/src/Hippo/Properties/launchSettings.json +++ b/src/Web/Properties/launchSettings.json @@ -22,7 +22,7 @@ "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "https://localhost:5309;http://localhost:5308", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Web/Services/CurrentUserService.cs b/src/Web/Services/CurrentUserService.cs new file mode 100644 index 000000000..d7faa433c --- /dev/null +++ b/src/Web/Services/CurrentUserService.cs @@ -0,0 +1,16 @@ +using System.Security.Claims; +using Hippo.Application.Common.Interfaces; + +namespace Hippo.Web.Services; + +public class CurrentUserService : ICurrentUserService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public CurrentUserService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public string? UserId => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); +} diff --git a/src/Hippo/Views/Account/Index.cshtml b/src/Web/Views/Account/Index.cshtml similarity index 100% rename from src/Hippo/Views/Account/Index.cshtml rename to src/Web/Views/Account/Index.cshtml diff --git a/src/Hippo/Views/Account/Login.cshtml b/src/Web/Views/Account/Login.cshtml similarity index 97% rename from src/Hippo/Views/Account/Login.cshtml rename to src/Web/Views/Account/Login.cshtml index 88f2d5887..28e3eccde 100644 --- a/src/Hippo/Views/Account/Login.cshtml +++ b/src/Web/Views/Account/Login.cshtml @@ -1,4 +1,4 @@ -@model Hippo.ViewModels.LoginForm +@model Hippo.Application.Accounts.Commands.LoginAccountCommand; @{ ViewData["Title"] = "Log In"; diff --git a/src/Hippo/Views/Account/Register.cshtml b/src/Web/Views/Account/Register.cshtml similarity index 83% rename from src/Hippo/Views/Account/Register.cshtml rename to src/Web/Views/Account/Register.cshtml index be8e4128c..a25616ac7 100644 --- a/src/Hippo/Views/Account/Register.cshtml +++ b/src/Web/Views/Account/Register.cshtml @@ -1,4 +1,4 @@ -@model Hippo.ViewModels.AccountRegisterForm +@model Hippo.Application.Accounts.Commands.CreateAccountCommand; @{ ViewData["Title"] = "Register"; @@ -21,16 +21,6 @@

-
- -
- - - - -

-
-
diff --git a/src/Hippo/Views/App/Delete.cshtml b/src/Web/Views/App/Delete.cshtml similarity index 97% rename from src/Hippo/Views/App/Delete.cshtml rename to src/Web/Views/App/Delete.cshtml index 13a316026..e4197111a 100644 --- a/src/Hippo/Views/App/Delete.cshtml +++ b/src/Web/Views/App/Delete.cshtml @@ -1,4 +1,4 @@ -@model Hippo.Models.Application +@model Hippo.Application.Apps.Queries.AppDto; @{ ViewData["Title"] = "Delete Application"; diff --git a/src/Hippo/Views/App/DeleteChannel.cshtml b/src/Web/Views/App/DeleteChannel.cshtml similarity index 94% rename from src/Hippo/Views/App/DeleteChannel.cshtml rename to src/Web/Views/App/DeleteChannel.cshtml index 4e5682fdd..4f2afc1b9 100644 --- a/src/Hippo/Views/App/DeleteChannel.cshtml +++ b/src/Web/Views/App/DeleteChannel.cshtml @@ -1,4 +1,4 @@ -@model Hippo.Models.Channel +@model Hippo.Application.Channels.Queries.ChannelDto; @{ ViewData["Title"] = "Delete Channel"; @@ -19,7 +19,7 @@
-
+ Cancel diff --git a/src/Web/Views/App/Details.cshtml b/src/Web/Views/App/Details.cshtml new file mode 100644 index 000000000..ec7c0f472 --- /dev/null +++ b/src/Web/Views/App/Details.cshtml @@ -0,0 +1,6 @@ +@model Hippo.Application.Apps.Queries.AppDto; + +@{ + ViewData["Title"] = @Html.DisplayFor(model => model.Name); + Layout = "_AppLayout"; +} diff --git a/src/Hippo/Views/App/Edit.cshtml b/src/Web/Views/App/Edit.cshtml similarity index 80% rename from src/Hippo/Views/App/Edit.cshtml rename to src/Web/Views/App/Edit.cshtml index 7f1544ba5..6ceec3611 100644 --- a/src/Hippo/Views/App/Edit.cshtml +++ b/src/Web/Views/App/Edit.cshtml @@ -1,4 +1,4 @@ -@model Hippo.ViewModels.AppEditForm +@model Hippo.Application.Apps.Commands.UpdateAppCommand; @{ ViewData["Title"] = "Edit Application"; @@ -48,18 +48,6 @@
-
-
- -
-
-
- -

-
-
-
-
diff --git a/src/Hippo/Views/App/EditChannel.cshtml b/src/Web/Views/App/EditChannel.cshtml similarity index 50% rename from src/Hippo/Views/App/EditChannel.cshtml rename to src/Web/Views/App/EditChannel.cshtml index ee84d4c88..2a8cc3eb9 100644 --- a/src/Hippo/Views/App/EditChannel.cshtml +++ b/src/Web/Views/App/EditChannel.cshtml @@ -1,4 +1,5 @@ -@model Hippo.ViewModels.AppEditChannelForm +@using Hippo.Core.Enums +@model Hippo.Application.Channels.Commands.UpdateChannelCommand; @{ ViewData["Title"] = "Configure Environment"; @@ -8,43 +9,18 @@
- - +
- +
- -
-
-
-
-
- -
-
-
- -

-
-
-
-
-
- -
-
-
- -

+
@@ -53,42 +29,42 @@
- @foreach (var strategy in Model.RevisionSelectionStrategies) { + @foreach (var strategy in Enum.GetValues(typeof(ChannelRevisionSelectionStrategy))) { } -

+

-
- + @* TODO: find out a way to select all app revisions *@ + This blank screen has been brought to your attention by Matt Fisher. He broke this. PLEASE FIX ME + @*
+
- +
-

+

-
+
*@
- +
- -

You can choose to deply all versions of your app (enter an *) or limit deploys to a specific range.

- +

Examples:
- To deploy minor releases that follow v1.0, set the range to 1<. To follow v1.0 but exclude releases beyond v2.0, set the range to 1-2.

-

+ To deploy any version of your app, set the range to *. To follow v1.0.0 but exclude releases beyond v2.0.0, set the range to ^1.0.0.

+

diff --git a/src/Hippo/Views/App/Index.cshtml b/src/Web/Views/App/Index.cshtml similarity index 92% rename from src/Hippo/Views/App/Index.cshtml rename to src/Web/Views/App/Index.cshtml index dc33c3958..929dee33d 100644 --- a/src/Hippo/Views/App/Index.cshtml +++ b/src/Web/Views/App/Index.cshtml @@ -1,4 +1,4 @@ -@model IEnumerable +@model Hippo.Application.Apps.Queries.AppsVm; @{ ViewData["Title"] = "Your Apps"; @@ -6,12 +6,13 @@ }
- @foreach (var item in Model) { + @foreach (var item in Model!.Apps) {

@Html.DisplayFor(modelItem => item.Name)

- @if (item.Status().Health == Hippo.Models.HealthLevel.Healthy) { + @* TODO: restore app health *@ + @* @if (item.Status().Health == Hippo.Models.HealthLevel.Healthy) {
@@ -33,7 +34,7 @@
- } + } *@

WASM Application

diff --git a/src/Hippo/Views/App/New.cshtml b/src/Web/Views/App/New.cshtml similarity index 97% rename from src/Hippo/Views/App/New.cshtml rename to src/Web/Views/App/New.cshtml index ce257bede..b83f73564 100644 --- a/src/Hippo/Views/App/New.cshtml +++ b/src/Web/Views/App/New.cshtml @@ -1,4 +1,4 @@ -@model Hippo.ViewModels.AppNewForm +@model Hippo.Application.Apps.Commands.CreateAppCommand; @{ ViewData["Title"] = "New Application"; diff --git a/src/Web/Views/App/NewChannel.cshtml b/src/Web/Views/App/NewChannel.cshtml new file mode 100644 index 000000000..a9a3958ba --- /dev/null +++ b/src/Web/Views/App/NewChannel.cshtml @@ -0,0 +1,103 @@ +@using Hippo.Core.Enums +@model Hippo.Application.Channels.Commands.CreateChannelCommand; + +@{ + ViewData["Title"] = "Add an Environment"; +} + +
+
+ + + +
+ +
+
+
+ +
+
+
+ +

+
+
+
+ +
+ +
+
+ @foreach (var strategy in Enum.GetValues(typeof(ChannelRevisionSelectionStrategy))) { + + } +

+ +
+ @* TODO: find out a way to select all app revisions *@ + This blank screen has been brought to your attention by Matt Fisher. He broke this. PLEASE FIX ME + @*
+ +
+
+
+
+
+ +
+
+

+
+
*@ +
+ +
+
+ +
+
+
+ +

You can choose to deply all versions of your app (enter an *) or limit deploys to a specific range.

+ +

Examples:
+ To deploy minor releases that follow v1.0, set the range to 1<. To follow v1.0 but exclude releases beyond v2.0, set the range to 1-2.

+

+
+
+
+
+
+
+
+
+
+ + Cancel +
+
+
+
+
+
+ +
+
+
+
+

+
+
+
+
+ +
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/src/Hippo/Views/App/RegisterRevision.cshtml b/src/Web/Views/App/RegisterRevision.cshtml similarity index 87% rename from src/Hippo/Views/App/RegisterRevision.cshtml rename to src/Web/Views/App/RegisterRevision.cshtml index a8c7c6251..d7e3e925f 100644 --- a/src/Hippo/Views/App/RegisterRevision.cshtml +++ b/src/Web/Views/App/RegisterRevision.cshtml @@ -1,4 +1,4 @@ -@model Hippo.ViewModels.AppRegisterRevisionForm +@model Hippo.Application.Revisions.Commands.CreateRevisionCommand; @{ ViewData["Title"] = "Revision"; @@ -9,7 +9,7 @@
- +
diff --git a/src/Hippo/Views/Shared/_AppLayout.cshtml b/src/Web/Views/Shared/_AppLayout.cshtml similarity index 100% rename from src/Hippo/Views/Shared/_AppLayout.cshtml rename to src/Web/Views/Shared/_AppLayout.cshtml diff --git a/src/Hippo/Views/Shared/_Footer.cshtml b/src/Web/Views/Shared/_Footer.cshtml similarity index 100% rename from src/Hippo/Views/Shared/_Footer.cshtml rename to src/Web/Views/Shared/_Footer.cshtml diff --git a/src/Hippo/Views/Shared/_Layout.cshtml b/src/Web/Views/Shared/_Layout.cshtml similarity index 80% rename from src/Hippo/Views/Shared/_Layout.cshtml rename to src/Web/Views/Shared/_Layout.cshtml index aaa25cc66..d89de98cd 100644 --- a/src/Hippo/Views/Shared/_Layout.cshtml +++ b/src/Web/Views/Shared/_Layout.cshtml @@ -1,4 +1,18 @@ - +@using Microsoft.AspNetCore.Identity; +@using Hippo.Infrastructure.Identity; +@inject SignInManager SignInManager; +@inject UserManager UserManager; + +@{ + string? returnUrl = null; + var query = ViewContext.HttpContext.Request.Query; + if (query.ContainsKey("returnUrl")) + { + returnUrl = query["returnUrl"]; + } +} + + @@ -11,7 +25,7 @@ - @if (User.Identity.IsAuthenticated) { + @if (SignInManager.IsSignedIn(User)) {
diff --git a/src/Hippo/Views/Shared/_NavAppTopbar.cshtml b/src/Web/Views/Shared/_NavAppTopbar.cshtml similarity index 96% rename from src/Hippo/Views/Shared/_NavAppTopbar.cshtml rename to src/Web/Views/Shared/_NavAppTopbar.cshtml index c15561ce3..8efd2d410 100644 --- a/src/Hippo/Views/Shared/_NavAppTopbar.cshtml +++ b/src/Web/Views/Shared/_NavAppTopbar.cshtml @@ -22,7 +22,8 @@