diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..00897be --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,45 @@ +name: .NET Build & Test + +on: + push: + branches: + - 'develop' + pull_request: + branches: + - '*' + workflow_dispatch: + +env: + DOTNET_VERSION: '8.0.x' + +jobs: + test: + name: Build & Test + runs-on: ubuntu-latest + env: + DatabaseConfiguration__UseInMemoryDb: true + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Restore Tools + run: | + cd Backend/Bones.Api + dotnet tool restore + cd ../.. + cd Frontend/Bones.Api.Client + dotnet tool restore + cd ../.. + + - name: Build + run: | + dotnet build --no-incremental + git diff --quiet || exit 1 + + - name: Test + run: dotnet test --no-build \ No newline at end of file diff --git a/.github/workflows/publish-client.yml b/.github/workflows/publish-client.yml new file mode 100644 index 0000000..214c4e5 --- /dev/null +++ b/.github/workflows/publish-client.yml @@ -0,0 +1,44 @@ +name: Build Client Artifact + +on: + push: + branches: + - 'develop' + pull_request: + branches: + - '*' + workflow_dispatch: + +env: + DOTNET_VERSION: '8.0.x' + +jobs: + client: + name: Publish Client + runs-on: ubuntu-latest + env: + DatabaseConfiguration__UseInMemoryDb: true + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Restore Tools + run: | + cd Frontend/Bones.Api.Client + dotnet tool restore + cd ../.. + + - name: Publish + run: | + dotnet publish Frontend/Bones.WebUI/Bones.WebUI.csproj --configuration Release --property PublishDir=~/publish + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: webui-wasm-client + path: ~/publish \ No newline at end of file diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/publish-server.yml similarity index 55% rename from .github/workflows/dotnetcore.yml rename to .github/workflows/publish-server.yml index 765a396..c70273e 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/publish-server.yml @@ -1,4 +1,4 @@ -name: .NET Build & Test +name: Build Server Artifacts on: push: @@ -13,8 +13,8 @@ env: DOTNET_VERSION: '8.0.x' jobs: - test: - name: .NET Build & Test + server: + name: Publish Client runs-on: ubuntu-latest env: DatabaseConfiguration__UseInMemoryDb: true @@ -32,31 +32,20 @@ jobs: cd Backend/Bones.Api dotnet tool restore cd ../.. - cd Frontend/Bones.Api.Client - dotnet tool restore - cd ../.. - - - name: Build - run: | - dotnet build --no-incremental - git diff --quiet || exit 1 - - - name: Test - run: dotnet test --no-build - name: Publish Client and Server run: | - dotnet publish Backend/Bones.Api/Bones.Api.csproj --configuration Release --runtime linux-x64 --self-contained true --property PublishDir=~/publish-server - dotnet publish Frontend/Bones.WebUI/Bones.WebUI.csproj --configuration Release --property PublishDir=~/publish-client + dotnet publish Backend/Bones.Api/Bones.Api.csproj --configuration Release --runtime linux-x64 --self-contained true --property PublishDir=~/publish-api + dotnet publish Backend/Bones.BackgroundService/Bones.BackgroundService.csproj --configuration Release --runtime linux-x64 --self-contained true --property PublishDir=~/publish-background-service - - name: Upload Server Build Artifact + - name: Upload API Build Artifact uses: actions/upload-artifact@v4 with: - name: server-linux-x64 - path: ~/publish-server + name: api-linux-x64 + path: ~/publish-api - - name: Upload Client Build Artifact + - name: Upload Background Service Build Artifact uses: actions/upload-artifact@v4 with: - name: client - path: ~/publish-client \ No newline at end of file + name: background-service-linux-x64 + path: ~/publish-background-service \ No newline at end of file diff --git a/Backend/Bones.Api/OpenApi/swagger.json b/Backend/Bones.Api/OpenApi/swagger.json index 1ad15a5..c7b2bba 100644 --- a/Backend/Bones.Api/OpenApi/swagger.json +++ b/Backend/Bones.Api/OpenApi/swagger.json @@ -32,6 +32,36 @@ } }, "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, "200": { "description": "OK", "content": { @@ -73,8 +103,8 @@ } }, "responses": { - "200": { - "description": "OK", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -83,8 +113,28 @@ } } }, - "401": { - "description": "Unauthorized", + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "200": { + "description": "OK", "content": { "application/json": { "schema": { @@ -133,8 +183,8 @@ } ], "responses": { - "200": { - "description": "OK", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -143,8 +193,28 @@ } } }, - "401": { - "description": "Unauthorized", + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "200": { + "description": "OK", "content": { "application/json": { "schema": { @@ -175,8 +245,8 @@ "required": true }, "responses": { - "200": { - "description": "OK", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -185,8 +255,28 @@ } } }, - "401": { - "description": "Unauthorized", + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "200": { + "description": "OK", "content": { "application/json": { "schema": { @@ -206,6 +296,36 @@ "summary": "", "operationId": "LogoutAsync", "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, "200": { "description": "OK", "content": { @@ -227,18 +347,28 @@ "summary": "", "operationId": "GetMyBasicInfoAsync", "responses": { - "200": { - "description": "OK", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetMyBasicInfoResponseActionResult" + "$ref": "#/components/schemas/EmptyResponseActionResult" } } } }, - "401": { - "description": "Unauthorized", + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", "content": { "application/json": { "schema": { @@ -246,6 +376,16 @@ } } } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyBasicInfoResponseActionResult" + } + } + } } } } @@ -268,6 +408,36 @@ } }, "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, "200": { "description": "OK", "content": { @@ -290,6 +460,108 @@ } } } + }, + "/Test/bones-exception": { + "get": { + "tags": [ + "Test" + ], + "summary": "Throws a BonesException to test the ApiExceptionHandler", + "operationId": "GetBonesExceptionAsync", + "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + } + } + } + }, + "/Test/forbidden": { + "get": { + "tags": [ + "Test" + ], + "summary": "Throws a ForbiddenAccessException to test the ApiExceptionHandler", + "operationId": "GetForbiddenAsync", + "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResponseActionResult" + } + } + } + } + } + } } }, "components": { diff --git a/Backend/Bones.Backend/Bones.Backend.csproj b/Backend/Bones.Backend/Bones.Backend.csproj index bad8754..c1eb8ae 100644 --- a/Backend/Bones.Backend/Bones.Backend.csproj +++ b/Backend/Bones.Backend/Bones.Backend.csproj @@ -25,6 +25,12 @@ + + + + + + diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs new file mode 100644 index 0000000..836b7ac --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs @@ -0,0 +1,11 @@ +using Bones.Database.DbSets.ProjectManagement; + +namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; + +internal sealed class GetInitiativeByIdHandler : IRequestHandler> +{ + public Task> Handle(GetInitiativeByIdQuery request, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs new file mode 100644 index 0000000..bee404a --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs @@ -0,0 +1,11 @@ +using Bones.Database.DbSets.AccountManagement; +using Bones.Database.DbSets.ProjectManagement; + +namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; + +/// +/// Gets the initiative by ID +/// +/// +/// +public sealed record GetInitiativeByIdQuery(Guid InitiativeId, BonesUser RequestingUser) : IRequest>; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiative.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs similarity index 59% rename from Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiative.cs rename to Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs index 0e3149a..93850ea 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiative.cs +++ b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs @@ -1,6 +1,6 @@ namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; -public class GetInitiative +internal sealed class GetInitiativeByIdQueryValidator { - + } \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativesByOwner/GetInitiatives.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativesByOwner/GetInitiatives.cs deleted file mode 100644 index b3a08e3..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativesByOwner/GetInitiatives.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativesByOwner; - -public class GetInitiatives -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/UpdateInitiativeById/UpdateInitiative.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/UpdateInitiativeById/UpdateInitiative.cs deleted file mode 100644 index db7afc1..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/UpdateInitiativeById/UpdateInitiative.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Initiatives.UpdateInitiativeById; - -public class UpdateInitiative -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateItem.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateItem.cs deleted file mode 100644 index 5d9eeb7..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateItem.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Bones.Database.Operations.ProjectManagement.WorkItems.CreateWorkItemDb; - -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class CreateItem(ISender sender) : IRequestHandler -{ - /// - /// DB Command for creating an WorkItem. - /// - /// Name of the item - /// Internal ID of the queue this item is in - /// Internal ID of the layout version this item is using - /// Internal ID of the queue - public record Command(string Name, Guid QueueId, Guid LayoutVersionId, Dictionary Values) - : IValidatableRequest - { - /// - public (bool valid, string? invalidReason) IsRequestValid() - { - if (string.IsNullOrWhiteSpace(Name)) - { - return (false, ""); - } - - if (QueueId == Guid.Empty) - { - return (false, ""); - } - - if (LayoutVersionId == Guid.Empty) - { - return (false, ""); - } - - return (true, null); - } - } - - /// - public async Task Handle(Command request, CancellationToken cancellationToken) - { - return await sender.Send(new CreateWorkItemDbHandler.Command(request.Name, request.QueueId, request.LayoutVersionId, request.Values), cancellationToken); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateTag.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateTag.cs deleted file mode 100644 index 16ef3a6..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/CreateTag.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Bones.Database.Operations.ProjectManagement.Tags.CreateTagDb; - -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class CreateTag(ISender sender) : IRequestHandler -{ - /// - /// DB Command for creating a Project. - /// - /// Name of the project - public record Command(string Name) : IValidatableRequest - { - /// - public (bool valid, string? invalidReason) IsRequestValid() - { - if (string.IsNullOrWhiteSpace(Name)) - { - return (false, ""); - } - - return (true, null); - } - } - - /// - public async Task Handle(Command request, CancellationToken cancellationToken) - { - return await sender.Send(new CreateTagDbCommand(request.Name), cancellationToken); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteItem.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteItem.cs deleted file mode 100644 index 1d9fd59..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteItem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class DeleteItem -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteTag.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteTag.cs deleted file mode 100644 index fb040fd..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/DeleteTag.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class DeleteTag -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItem.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItem.cs deleted file mode 100644 index 1706c25..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class GetItem -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItems.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItems.cs deleted file mode 100644 index ffe33e3..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/GetItems.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class GetItems -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateItem.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateItem.cs deleted file mode 100644 index afcfaae..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateItem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class UpdateItem -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateTag.cs b/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateTag.cs deleted file mode 100644 index dd9026f..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Items/UpdateTag.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Items; - -public class UpdateTag -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/CreateLayout.cs b/Backend/Bones.Backend/Features/ProjectManagement/Layouts/CreateLayout.cs deleted file mode 100644 index 99743d2..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/CreateLayout.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Bones.Database.Operations.ProjectManagement.WorkItemLayouts.CreateWorkItemLayoutDb; - -namespace Bones.Backend.Features.ProjectManagement.Layouts; - -public class CreateLayout(ISender sender) : IRequestHandler -{ - /// - /// DB Command for creating a layout. - /// - /// Name of the layout - /// Internal IDs of the fields this layout is using - public record Command(string Name, List ItemFieldIds) : IValidatableRequest - { - /// - public (bool valid, string? invalidReason) IsRequestValid() - { - if (string.IsNullOrWhiteSpace(Name)) - { - return (false, ""); - } - - foreach (Guid id in ItemFieldIds) - { - if (id == Guid.Empty) - { - return (false, ""); - } - } - - return (true, null); - } - } - - /// - public async Task Handle(Command request, CancellationToken cancellationToken) - { - return await sender.Send(new CreateWorkItemLayoutDbCommand(request.Name, request.ItemFieldIds), cancellationToken); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/DeleteLayout.cs b/Backend/Bones.Backend/Features/ProjectManagement/Layouts/DeleteLayout.cs deleted file mode 100644 index deb8151..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/DeleteLayout.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Layouts; - -public class DeleteLayout -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayout.cs b/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayout.cs deleted file mode 100644 index a0d6395..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayout.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Layouts; - -public class GetLayout -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayouts.cs b/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayouts.cs deleted file mode 100644 index 1c1aa3a..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/GetLayouts.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Layouts; - -public class GetLayouts -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/UpdateLayout.cs b/Backend/Bones.Backend/Features/ProjectManagement/Layouts/UpdateLayout.cs deleted file mode 100644 index 21e3dd9..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Layouts/UpdateLayout.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Layouts; - -public class UpdateLayout -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/DeleteProjectById/DeleteProject.cs b/Backend/Bones.Backend/Features/ProjectManagement/Projects/DeleteProjectById/DeleteProject.cs deleted file mode 100644 index f297c64..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/DeleteProjectById/DeleteProject.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Projects.DeleteProjectById; - -public class DeleteProject -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProject.cs b/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProject.cs deleted file mode 100644 index 2ddf8d7..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProject.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectById; - -public class GetProject -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjects.cs b/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjects.cs deleted file mode 100644 index b4cbf53..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjects.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; - -public class GetProjects -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UpdateProject/UpdateProject.cs b/Backend/Bones.Backend/Features/ProjectManagement/Projects/UpdateProject/UpdateProject.cs deleted file mode 100644 index ce71cf1..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UpdateProject/UpdateProject.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Projects.UpdateProject; - -public class UpdateProject -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue.cs deleted file mode 100644 index b9896df..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Bones.Database.Operations.ProjectManagement.Queues.CreateQueueDb; - -namespace Bones.Backend.Features.ProjectManagement.Queues; - -public class CreateQueue(ISender sender) : IRequestHandler -{ - /// - /// DB Command for creating a Queue. - /// - /// Name of the queue - /// Internal ID of the initiative - public record Command(string Name, Guid InitiativeId) : IValidatableRequest - { - /// - public (bool valid, string? invalidReason) IsRequestValid() - { - if (string.IsNullOrWhiteSpace(Name)) - { - return (false, ""); - } - - if (InitiativeId == Guid.Empty) - { - return (false, ""); - } - - return (true, null); - } - } - - /// - public async Task Handle(Command request, CancellationToken cancellationToken) - { - return await sender.Send(new CreateQueueDbHandler.Command(request.Name, request.InitiativeId), cancellationToken); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs new file mode 100644 index 0000000..55b69dd --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs @@ -0,0 +1,11 @@ +using Bones.Database.DbSets.AccountManagement; + +namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; + +/// +/// Command for creating a Queue. +/// +/// Name of the queue +/// Internal ID of the initiative +/// +public sealed record CreateQueueCommand(string Name, Guid InitiativeId, BonesUser RequestingUser) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs new file mode 100644 index 0000000..9352d9e --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; + +internal sealed class CreateQueueCommandValidator : AbstractValidator +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs new file mode 100644 index 0000000..3dcb781 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs @@ -0,0 +1,12 @@ +using Bones.Database.Operations.ProjectManagement.Queues.CreateQueueDb; + +namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; + +internal sealed class CreateQueueHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateQueueCommand request, CancellationToken cancellationToken) + { + // TODO: Check permission + return await sender.Send(new CreateQueueDbCommand(request.Name, request.InitiativeId), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/DeleteQueue.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/DeleteQueue.cs deleted file mode 100644 index d98e30d..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/DeleteQueue.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Queues; - -public class DeleteQueue -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueue.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueue.cs deleted file mode 100644 index 68e55a7..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueue.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Queues; - -public class GetQueue -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueues.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueues.cs deleted file mode 100644 index 68e737d..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/GetQueues.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Queues; - -public class GetQueues -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/UpdateQueue.cs b/Backend/Bones.Backend/Features/ProjectManagement/Queues/UpdateQueue.cs deleted file mode 100644 index c6b2ec5..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/UpdateQueue.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Queues; - -public class UpdateQueue -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagCommand.cs new file mode 100644 index 0000000..3d7009e --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagCommand.cs @@ -0,0 +1,7 @@ +namespace Bones.Backend.Features.ProjectManagement.Tags.CreateTag; + +/// +/// Command for creating a tag. +/// +/// Name of the tag +public sealed record CreateTagCommand(string Name) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagHandler.cs new file mode 100644 index 0000000..6d4fe62 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/Tags/CreateTag/CreateTagHandler.cs @@ -0,0 +1,11 @@ +using Bones.Database.Operations.ProjectManagement.Tags.CreateTagDb; + +namespace Bones.Backend.Features.ProjectManagement.Tags.CreateTag; + +internal sealed class CreateTagHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateTagCommand request, CancellationToken cancellationToken) + { + return await sender.Send(new CreateTagDbCommand(request.Name), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutCommand.cs new file mode 100644 index 0000000..ec3a938 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutCommand.cs @@ -0,0 +1,8 @@ +namespace Bones.Backend.Features.ProjectManagement.WorkItemLayouts; + +/// +/// Command for creating a layout. +/// +/// Internal IDs of the project this layout should belong to +/// Name of the layout +public record CreateLayoutCommand(Guid ProjectId, string Name) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutHandler.cs new file mode 100644 index 0000000..92fdf43 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItemLayouts/CreateLayoutHandler.cs @@ -0,0 +1,11 @@ +using Bones.Database.Operations.ProjectManagement.WorkItemLayouts.CreateWorkItemLayoutDb; + +namespace Bones.Backend.Features.ProjectManagement.WorkItemLayouts; + +internal sealed class CreateLayoutHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateLayoutCommand request, CancellationToken cancellationToken) + { + return await sender.Send(new CreateWorkItemLayoutDbCommand(request.ProjectId, request.Name), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs new file mode 100644 index 0000000..4104aa7 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs @@ -0,0 +1,10 @@ +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; + +/// +/// DB Command for creating a WorkItem. +/// +/// Name of the item +/// Internal ID of the queue this item is in +/// Internal ID of the layout version this item is using +/// Internal ID of the queue +public sealed record CreateWorkItemCommand(string Name, Guid QueueId, Guid WorkItemLayoutVersionId, Dictionary Values) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs new file mode 100644 index 0000000..5a216de --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; + +internal sealed class CreateWorkItemCommandValidator : AbstractValidator +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs new file mode 100644 index 0000000..bd85e78 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs @@ -0,0 +1,11 @@ +using Bones.Database.Operations.ProjectManagement.WorkItems.CreateWorkItemDb; + +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; + +internal sealed class CreateWorkItemHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateWorkItemCommand request, CancellationToken cancellationToken) + { + return await sender.Send(new CreateWorkItemDbCommand(request.Name, request.QueueId, request.WorkItemLayoutVersionId, request.Values), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs new file mode 100644 index 0000000..d8eb748 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs @@ -0,0 +1,9 @@ +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; + +/// +/// DB Command for creating a WorkItem. +/// +/// Internal ID of the queue this item is in +/// Internal ID of the layout version this item is using +/// Internal ID of the queue +public sealed record CreateWorkItemVersionCommand(Guid WorkItemId, Guid WorkItemLayoutVersionId, Dictionary Values) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs new file mode 100644 index 0000000..e2a0646 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; + +internal sealed class CreateWorkItemVersionCommandValidator : AbstractValidator +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs new file mode 100644 index 0000000..c7cb8a5 --- /dev/null +++ b/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs @@ -0,0 +1,11 @@ +using Bones.Database.Operations.ProjectManagement.WorkItems.CreateWorkItemVersionDb; + +namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; + +internal sealed class CreateWorkItemVersionHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateWorkItemVersionCommand request, CancellationToken cancellationToken) + { + return await sender.Send(new CreateWorkItemVersionDbCommand(request.WorkItemId, request.WorkItemLayoutVersionId, request.Values), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.BackgroundService/GlobalUsings.cs b/Backend/Bones.BackgroundService/GlobalUsings.cs index 041a6d1..6c30d5a 100644 --- a/Backend/Bones.BackgroundService/GlobalUsings.cs +++ b/Backend/Bones.BackgroundService/GlobalUsings.cs @@ -1,6 +1,4 @@ global using MediatR; global using Serilog; global using FluentValidation; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; global using Bones.Shared.Backend.Models; \ No newline at end of file diff --git a/Backend/Bones.BackgroundService/Tasks/Minutely/MinutelyTaskBase.cs b/Backend/Bones.BackgroundService/Tasks/Minutely/MinutelyTaskBase.cs index 8b829d6..52fc869 100644 --- a/Backend/Bones.BackgroundService/Tasks/Minutely/MinutelyTaskBase.cs +++ b/Backend/Bones.BackgroundService/Tasks/Minutely/MinutelyTaskBase.cs @@ -1,8 +1,10 @@ namespace Bones.BackgroundService.Tasks.Minutely; -internal abstract class MinutelyTaskBase(ILogger logger, ISender sender) : TaskBase(logger, sender) +internal abstract class MinutelyTaskBase(ISender sender) : TaskBase(sender) { - protected override TimeSpan? Interval { get; } = TimeSpan.FromMinutes(1); + protected override TimeSpan Interval { get; } = TimeSpan.FromMinutes(1); + + protected override bool IsStartupOnlyTask { get; set; } = false; protected override Task ShouldTaskRunAsync(CancellationToken cancellationToken) => Task.FromResult(IsEnabled); } \ No newline at end of file diff --git a/Backend/Bones.BackgroundService/Tasks/Minutely/SendConfirmationEmailTask.cs b/Backend/Bones.BackgroundService/Tasks/Minutely/SendConfirmationEmailTask.cs index e15ae27..06a2525 100644 --- a/Backend/Bones.BackgroundService/Tasks/Minutely/SendConfirmationEmailTask.cs +++ b/Backend/Bones.BackgroundService/Tasks/Minutely/SendConfirmationEmailTask.cs @@ -1,6 +1,6 @@ namespace Bones.BackgroundService.Tasks.Minutely; -internal class SendConfirmationEmailTask(ILogger logger, ISender sender) : MinutelyTaskBase(logger, sender) +internal class SendConfirmationEmailTask(ISender sender) : MinutelyTaskBase(sender) { protected override Task ShouldTaskRunAsync(CancellationToken cancellationToken) { @@ -11,7 +11,7 @@ protected override Task ShouldTaskRunAsync(CancellationToken cancellationT // TODO: Check if there are any emails in the queue - return Task.FromResult(false); + return Task.FromResult(true); } protected override Task RunTaskAsync(CancellationToken cancellationToken) diff --git a/Backend/Bones.BackgroundService/Tasks/Startup/SetupBackgroundTaskUserTask.cs b/Backend/Bones.BackgroundService/Tasks/Startup/SetupBackgroundTaskUserTask.cs index 3d353c2..12aee9d 100644 --- a/Backend/Bones.BackgroundService/Tasks/Startup/SetupBackgroundTaskUserTask.cs +++ b/Backend/Bones.BackgroundService/Tasks/Startup/SetupBackgroundTaskUserTask.cs @@ -1,9 +1,19 @@ namespace Bones.BackgroundService.Tasks.Startup; -internal class SetupBackgroundTaskUserTask(Logger logger, ISender sender) : StartupTaskBase(logger, sender) +internal class SetupBackgroundTaskUserTask(ISender sender) : StartupTaskBase(sender) { protected override Task RunTaskAsync(CancellationToken cancellationToken) { - throw new NotImplementedException(); + // TODO: + // Get the email for the background task user + // Get the optional userId for if you want to change the email after + // If the user id is set, get that user + // if the users email matches whats configured, do nothing + // if the email doesn't match, update it + // If its not set, check if any users in the DB have the configured email + // If none exist with that email, create the user + // If it exists, do nothing + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Backend/Bones.BackgroundService/Tasks/Startup/StartupTaskBase.cs b/Backend/Bones.BackgroundService/Tasks/Startup/StartupTaskBase.cs index cb4b546..ec5c7fe 100644 --- a/Backend/Bones.BackgroundService/Tasks/Startup/StartupTaskBase.cs +++ b/Backend/Bones.BackgroundService/Tasks/Startup/StartupTaskBase.cs @@ -1,8 +1,10 @@ namespace Bones.BackgroundService.Tasks.Startup; -internal abstract class StartupTaskBase(ILogger logger, ISender sender) : TaskBase(logger, sender) +internal abstract class StartupTaskBase(ISender sender) : TaskBase(sender) { - protected override TimeSpan? Interval { get; } = null; + protected override TimeSpan Interval { get; } = TimeSpan.FromSeconds(1); + + protected override bool IsStartupOnlyTask { get; set; } = true; protected override Task ShouldTaskRunAsync(CancellationToken cancellationToken) => Task.FromResult(IsEnabled); } \ No newline at end of file diff --git a/Backend/Bones.BackgroundService/Tasks/TaskBase.cs b/Backend/Bones.BackgroundService/Tasks/TaskBase.cs index ac9259f..3ba94e8 100644 --- a/Backend/Bones.BackgroundService/Tasks/TaskBase.cs +++ b/Backend/Bones.BackgroundService/Tasks/TaskBase.cs @@ -1,11 +1,15 @@ +using Serilog.Events; + namespace Bones.BackgroundService.Tasks; -internal abstract class TaskBase(ILogger logger, ISender sender) : Microsoft.Extensions.Hosting.BackgroundService +internal abstract class TaskBase(ISender sender) : Microsoft.Extensions.Hosting.BackgroundService { protected readonly ISender Sender = sender; - protected abstract TimeSpan? Interval { get; } + protected abstract TimeSpan Interval { get; } protected bool IsEnabled { get; set; } = true; + + protected abstract bool IsStartupOnlyTask { get; set; } protected abstract Task ShouldTaskRunAsync(CancellationToken cancellationToken); @@ -13,7 +17,7 @@ internal abstract class TaskBase(ILogger logger, ISender sender) : Microso protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - using PeriodicTimer timer = new(Interval ?? TimeSpan.MaxValue); + using PeriodicTimer timer = new(Interval); while (!cancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync(cancellationToken)) { @@ -24,34 +28,33 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) try { - if (logger.IsEnabled(LogLevel.Information)) + if (Log.IsEnabled(LogEventLevel.Information)) { - logger.LogInformation("{Name} started at: {Time}", typeof(T).Name, DateTimeOffset.Now); + Log.Information("{Name} started at: {Time}", GetType().Name, DateTimeOffset.Now); } await RunTaskAsync(cancellationToken); - if (logger.IsEnabled(LogLevel.Information)) + if (Log.IsEnabled(LogEventLevel.Information)) { - logger.LogInformation("{Name} finished at: {Time}", typeof(T).Name, DateTimeOffset.Now); + Log.Information("{Name} finished at: {Time}", GetType().Name, DateTimeOffset.Now); } } catch (Exception ex) { // TODO: add to TaskErrors table IsEnabled = false; - logger.LogError(ex, + Log.Error(ex, "{Time} | {Name} has unhandled exception: {ExceptionMessage}\n{ExceptionStackTrace}", - DateTimeOffset.Now, typeof(T).Name, ex.Message, ex.StackTrace); + DateTimeOffset.Now, GetType().Name, ex.Message, ex.StackTrace); } finally { - // Startup only task - if (Interval is null) + if (IsStartupOnlyTask) { IsEnabled = false; - // Lets also cancel it to clear this from memory + // Lets also cancel it await CancellationTokenSource.CreateLinkedTokenSource(cancellationToken).CancelAsync(); } } diff --git a/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.Designer.cs b/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.Designer.cs new file mode 100644 index 0000000..fc921ad --- /dev/null +++ b/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.Designer.cs @@ -0,0 +1,1152 @@ +// +using System; +using Bones.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bones.Database.Migrations +{ + [DbContext(typeof(BonesDbContext))] + [Migration("20240908032021_IDontRememberWhatIChanged")] + partial class IDontRememberWhatIChanged + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("IsSystemRole") + .HasColumnType("boolean"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReadOnlyRole") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.HasIndex("OrganizationId"); + + b.ToTable("BonesRoles", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("BonesRoleClaims", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("DisplayName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("EmailConfirmedDateTime") + .HasColumnType("timestamp with time zone"); + + 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("PasswordExpired") + .HasColumnType("boolean"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + 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.ToTable("BonesUsers", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BonesUserClaims", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("BonesUserLogins", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("BonesUserRoles", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("BonesUserTokens", "AccountManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetLayoutVersionId") + .HasColumnType("uuid"); + + b.Property("IsRequired") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AssetLayoutVersionId"); + + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + + b.ToTable("AssetFields", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetFieldListEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MatchingType") + .HasColumnType("integer"); + + b.Property("ParentFieldId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentFieldId"); + + b.ToTable("AssetFieldListEntries", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + + b.ToTable("AssetLayouts", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetLayoutId") + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AssetLayoutId"); + + b.ToTable("AssetLayoutVersions", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + + b.ToTable("Assets", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetVersionId") + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("FieldId") + .HasColumnType("uuid"); + + b.Property("LocationType") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AssetVersionId"); + + b.HasIndex("FieldId"); + + b.ToTable("AssetValues", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetId") + .HasColumnType("uuid"); + + b.Property("AssetLayoutId") + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.HasIndex("AssetLayoutId"); + + b.ToTable("AssetVersions", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.ToTable("BonesOrganizations", "OrganizationManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Initiative", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Initiatives", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + + b.ToTable("Projects", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Queue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("InitiativeId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("InitiativeId"); + + b.ToTable("Queues", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("WorkItemId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkItemId"); + + b.ToTable("Tags", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsRequired") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("WorkItemLayoutVersionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkItemLayoutVersionId"); + + b.ToTable("WorkItemFields", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemFieldListEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("MatchingType") + .HasColumnType("integer"); + + b.Property("ParentFieldId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ParentFieldId"); + + b.ToTable("WorkItemFieldListEntries", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("WorkItemLayouts", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.Property("WorkItemLayoutId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkItemLayoutId"); + + b.ToTable("WorkItemLayoutVersions", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddedToQueueDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("QueueId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("QueueId"); + + b.ToTable("WorkItems", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("FieldId") + .HasColumnType("uuid"); + + b.Property("LocationType") + .HasColumnType("integer"); + + b.Property("Value") + .HasColumnType("text"); + + b.Property("WorkItemVersionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("FieldId"); + + b.HasIndex("WorkItemVersionId"); + + b.ToTable("WorkItemValues", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.Property("WorkItemId") + .HasColumnType("uuid"); + + b.Property("WorkItemLayoutId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkItemId"); + + b.HasIndex("WorkItemLayoutId"); + + b.ToTable("WorkItemVersions", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.System.TaskError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ErrorMessage") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StackTrace") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TaskErrors", "System"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesRole", b => + { + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "Organization") + .WithMany("Roles") + .HasForeignKey("OrganizationId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesRoleClaim", b => + { + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserClaim", b => + { + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserLogin", b => + { + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserRole", b => + { + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUserToken", b => + { + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", null) + .WithMany("Fields") + .HasForeignKey("AssetLayoutVersionId"); + + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany() + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany() + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetFieldListEntry", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", "ParentField") + .WithMany("PossibleValues") + .HasForeignKey("ParentFieldId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ParentField"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => + { + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany("AssetLayouts") + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany("AssetLayouts") + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", "AssetLayout") + .WithMany("Versions") + .HasForeignKey("AssetLayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssetLayout"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => + { + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany("Assets") + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany("Assets") + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetValue", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", null) + .WithMany("Values") + .HasForeignKey("AssetVersionId"); + + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", "Field") + .WithMany() + .HasForeignKey("FieldId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Field"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.Assets.Asset", "Asset") + .WithMany("Versions") + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", "AssetLayout") + .WithMany() + .HasForeignKey("AssetLayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Asset"); + + b.Navigation("AssetLayout"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Initiative", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.Project", "Project") + .WithMany("Initiatives") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Project", b => + { + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany("Projects") + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany("Projects") + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Queue", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.Initiative", "Initiative") + .WithMany("Queues") + .HasForeignKey("InitiativeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Initiative"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Tag", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", null) + .WithMany("Tags") + .HasForeignKey("WorkItemId"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", null) + .WithMany("Fields") + .HasForeignKey("WorkItemLayoutVersionId"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemFieldListEntry", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", "ParentField") + .WithMany("PossibleValues") + .HasForeignKey("ParentFieldId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ParentField"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", "WorkItemLayout") + .WithMany("Versions") + .HasForeignKey("WorkItemLayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkItemLayout"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.Queue", "Queue") + .WithMany("WorkItems") + .HasForeignKey("QueueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Queue"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemValue", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", "Field") + .WithMany() + .HasForeignKey("FieldId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", null) + .WithMany("Values") + .HasForeignKey("WorkItemVersionId"); + + b.Navigation("Field"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", "WorkItem") + .WithMany("Versions") + .HasForeignKey("WorkItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", "WorkItemLayout") + .WithMany() + .HasForeignKey("WorkItemLayoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkItem"); + + b.Navigation("WorkItemLayout"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUser", b => + { + b.Navigation("AssetLayouts"); + + b.Navigation("Assets"); + + b.Navigation("Projects"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => + { + b.Navigation("PossibleValues"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => + { + b.Navigation("Values"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", b => + { + b.Navigation("AssetLayouts"); + + b.Navigation("Assets"); + + b.Navigation("Projects"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Initiative", b => + { + b.Navigation("Queues"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Project", b => + { + b.Navigation("Initiatives"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Queue", b => + { + b.Navigation("WorkItems"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => + { + b.Navigation("PossibleValues"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => + { + b.Navigation("Fields"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => + { + b.Navigation("Tags"); + + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => + { + b.Navigation("Values"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.cs b/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.cs new file mode 100644 index 0000000..81dda06 --- /dev/null +++ b/Backend/Bones.Database/Migrations/20240908032021_IDontRememberWhatIChanged.cs @@ -0,0 +1,302 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bones.Database.Migrations +{ + /// + public partial class IDontRememberWhatIChanged : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "System"); + + migrationBuilder.AddColumn( + name: "ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "DeleteFlag", + schema: "ProjectManagement", + table: "Initiatives", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AlterColumn( + name: "DisplayName", + schema: "AccountManagement", + table: "BonesUsers", + type: "character varying(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "ReadOnlyRole", + schema: "AccountManagement", + table: "BonesRoles", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "OwnerType", + schema: "AssetManagement", + table: "AssetLayouts", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "OwnerType", + schema: "AssetManagement", + table: "AssetFields", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "OwningUserId", + schema: "AssetManagement", + table: "AssetFields", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "TaskErrors", + schema: "System", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ErrorTime = table.Column(type: "timestamp with time zone", nullable: false), + ErrorMessage = table.Column(type: "text", nullable: false), + StackTrace = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TaskErrors", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_WorkItemLayouts_ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_AssetLayouts_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts", + column: "OwningOrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_AssetLayouts_OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts", + column: "OwningUserId"); + + migrationBuilder.CreateIndex( + name: "IX_AssetFields_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields", + column: "OwningOrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_AssetFields_OwningUserId", + schema: "AssetManagement", + table: "AssetFields", + column: "OwningUserId"); + + migrationBuilder.AddForeignKey( + name: "FK_AssetFields_BonesOrganizations_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields", + column: "OwningOrganizationId", + principalSchema: "OrganizationManagement", + principalTable: "BonesOrganizations", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_AssetFields_BonesUsers_OwningUserId", + schema: "AssetManagement", + table: "AssetFields", + column: "OwningUserId", + principalSchema: "AccountManagement", + principalTable: "BonesUsers", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_AssetLayouts_BonesOrganizations_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts", + column: "OwningOrganizationId", + principalSchema: "OrganizationManagement", + principalTable: "BonesOrganizations", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_AssetLayouts_BonesUsers_OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts", + column: "OwningUserId", + principalSchema: "AccountManagement", + principalTable: "BonesUsers", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_WorkItemLayouts_Projects_ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts", + column: "ProjectId", + principalSchema: "ProjectManagement", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AssetFields_BonesOrganizations_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropForeignKey( + name: "FK_AssetFields_BonesUsers_OwningUserId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropForeignKey( + name: "FK_AssetLayouts_BonesOrganizations_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropForeignKey( + name: "FK_AssetLayouts_BonesUsers_OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropForeignKey( + name: "FK_WorkItemLayouts_Projects_ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts"); + + migrationBuilder.DropTable( + name: "TaskErrors", + schema: "System"); + + migrationBuilder.DropIndex( + name: "IX_WorkItemLayouts_ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts"); + + migrationBuilder.DropIndex( + name: "IX_AssetLayouts_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropIndex( + name: "IX_AssetLayouts_OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropIndex( + name: "IX_AssetFields_OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropIndex( + name: "IX_AssetFields_OwningUserId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropColumn( + name: "ProjectId", + schema: "ProjectManagement", + table: "WorkItemLayouts"); + + migrationBuilder.DropColumn( + name: "DeleteFlag", + schema: "ProjectManagement", + table: "Initiatives"); + + migrationBuilder.DropColumn( + name: "ReadOnlyRole", + schema: "AccountManagement", + table: "BonesRoles"); + + migrationBuilder.DropColumn( + name: "OwnerType", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropColumn( + name: "OwningOrganizationId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropColumn( + name: "OwningUserId", + schema: "AssetManagement", + table: "AssetLayouts"); + + migrationBuilder.DropColumn( + name: "OwnerType", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropColumn( + name: "OwningOrganizationId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.DropColumn( + name: "OwningUserId", + schema: "AssetManagement", + table: "AssetFields"); + + migrationBuilder.AlterColumn( + name: "DisplayName", + schema: "AccountManagement", + table: "BonesUsers", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256, + oldNullable: true); + } + } +} diff --git a/Backend/Bones.Database/Migrations/BonesDbContextModelSnapshot.cs b/Backend/Bones.Database/Migrations/BonesDbContextModelSnapshot.cs index ca89c56..e94125f 100644 --- a/Backend/Bones.Database/Migrations/BonesDbContextModelSnapshot.cs +++ b/Backend/Bones.Database/Migrations/BonesDbContextModelSnapshot.cs @@ -46,6 +46,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("uuid"); + b.Property("ReadOnlyRole") + .HasColumnType("boolean"); + b.HasKey("Id"); b.HasIndex("NormalizedName") @@ -95,7 +98,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("DisplayName") - .HasColumnType("text"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Email") .HasMaxLength(256) @@ -234,13 +238,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("BonesUserTokens", "AccountManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Asset", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("DeleteFlag") + b.Property("AssetLayoutVersionId") + .HasColumnType("uuid"); + + b.Property("IsRequired") .HasColumnType("boolean"); b.Property("Name") @@ -257,32 +264,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OwningUserId") .HasColumnType("uuid"); - b.HasKey("Id"); - - b.HasIndex("OwningOrganizationId"); - - b.HasIndex("OwningUserId"); - - b.ToTable("Assets", "AssetManagement"); - }); - - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AssetLayoutVersionId") - .HasColumnType("uuid"); - - b.Property("IsRequired") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - b.Property("Type") .HasColumnType("integer"); @@ -290,10 +271,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("AssetLayoutVersionId"); + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + b.ToTable("AssetFields", "AssetManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFieldListEntry", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetFieldListEntry", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -316,7 +301,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AssetFieldListEntries", "AssetManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayout", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -330,12 +315,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(512) .HasColumnType("character varying(512)"); + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + b.HasKey("Id"); + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + b.ToTable("AssetLayouts", "AssetManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -357,7 +355,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AssetLayoutVersions", "AssetManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetValue", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerType") + .HasColumnType("integer"); + + b.Property("OwningOrganizationId") + .HasColumnType("uuid"); + + b.Property("OwningUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OwningOrganizationId"); + + b.HasIndex("OwningUserId"); + + b.ToTable("Assets", "AssetManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetValue", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -387,7 +417,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AssetValues", "AssetManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -436,6 +466,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("DeleteFlag") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() .HasMaxLength(512) @@ -528,34 +561,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tags", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AddedToQueueDateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("DeleteFlag") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("QueueId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("QueueId"); - - b.ToTable("WorkItems", "ProjectManagement"); - }); - - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemField", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -582,7 +588,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WorkItemFields", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFieldListEntry", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemFieldListEntry", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -605,7 +611,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WorkItemFieldListEntries", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayout", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -619,12 +625,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(512) .HasColumnType("character varying(512)"); + b.Property("ProjectId") + .HasColumnType("uuid"); + b.HasKey("Id"); + b.HasIndex("ProjectId"); + b.ToTable("WorkItemLayouts", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -646,7 +657,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WorkItemLayoutVersions", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemValue", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddedToQueueDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeleteFlag") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("QueueId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("QueueId"); + + b.ToTable("WorkItems", "ProjectManagement"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemValue", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -676,7 +714,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WorkItemValues", "ProjectManagement"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -703,6 +741,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WorkItemVersions", "ProjectManagement"); }); + modelBuilder.Entity("Bones.Database.DbSets.System.TaskError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ErrorMessage") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StackTrace") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TaskErrors", "System"); + }); + modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesRole", b => { b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "Organization") @@ -763,14 +823,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Asset", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => { + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", null) + .WithMany("Fields") + .HasForeignKey("AssetLayoutVersionId"); + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") - .WithMany("Assets") + .WithMany() .HasForeignKey("OwningOrganizationId"); b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") - .WithMany("Assets") + .WithMany() .HasForeignKey("OwningUserId"); b.Navigation("OwningOrganization"); @@ -778,16 +842,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("OwningUser"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetField", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetFieldListEntry", b => { - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayoutVersion", null) - .WithMany("Fields") - .HasForeignKey("AssetLayoutVersionId"); - }); - - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFieldListEntry", b => - { - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetField", "ParentField") + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", "ParentField") .WithMany("PossibleValues") .HasForeignKey("ParentFieldId") .OnDelete(DeleteBehavior.Cascade) @@ -796,9 +853,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("ParentField"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => { - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayout", "AssetLayout") + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany("AssetLayouts") + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany("AssetLayouts") + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", "AssetLayout") .WithMany("Versions") .HasForeignKey("AssetLayoutId") .OnDelete(DeleteBehavior.Cascade) @@ -807,13 +879,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AssetLayout"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetValue", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => { - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetVersion", null) + b.HasOne("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", "OwningOrganization") + .WithMany("Assets") + .HasForeignKey("OwningOrganizationId"); + + b.HasOne("Bones.Database.DbSets.AccountManagement.BonesUser", "OwningUser") + .WithMany("Assets") + .HasForeignKey("OwningUserId"); + + b.Navigation("OwningOrganization"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetValue", b => + { + b.HasOne("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", null) .WithMany("Values") .HasForeignKey("AssetVersionId"); - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetField", "Field") + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", "Field") .WithMany() .HasForeignKey("FieldId") .OnDelete(DeleteBehavior.Cascade) @@ -822,15 +909,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Field"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => { - b.HasOne("Bones.Database.DbSets.AssetManagement.Asset", "Asset") - .WithMany() + b.HasOne("Bones.Database.DbSets.AssetManagement.Assets.Asset", "Asset") + .WithMany("Versions") .HasForeignKey("AssetId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayoutVersion", "AssetLayout") + b.HasOne("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", "AssetLayout") .WithMany() .HasForeignKey("AssetLayoutId") .OnDelete(DeleteBehavior.Cascade) @@ -880,32 +967,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Tag", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItem", null) + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", null) .WithMany("Tags") .HasForeignKey("WorkItemId"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItem", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.Queue", "Queue") - .WithMany("Items") - .HasForeignKey("QueueId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Queue"); - }); - - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemField", b => - { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayoutVersion", null) + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", null) .WithMany("Fields") .HasForeignKey("WorkItemLayoutVersionId"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFieldListEntry", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemFieldListEntry", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemField", "ParentField") + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", "ParentField") .WithMany("PossibleValues") .HasForeignKey("ParentFieldId") .OnDelete(DeleteBehavior.Cascade) @@ -914,9 +990,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("ParentField"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayout", "WorkItemLayout") + b.HasOne("Bones.Database.DbSets.ProjectManagement.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", "WorkItemLayout") .WithMany("Versions") .HasForeignKey("WorkItemLayoutId") .OnDelete(DeleteBehavior.Cascade) @@ -925,30 +1012,41 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("WorkItemLayout"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemValue", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemField", "Field") + b.HasOne("Bones.Database.DbSets.ProjectManagement.Queue", "Queue") + .WithMany("WorkItems") + .HasForeignKey("QueueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Queue"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemValue", b => + { + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", "Field") .WithMany() .HasForeignKey("FieldId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemVersion", null) + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", null) .WithMany("Values") .HasForeignKey("WorkItemVersionId"); b.Navigation("Field"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => { - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItem", "WorkItem") + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", "WorkItem") .WithMany("Versions") .HasForeignKey("WorkItemId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayoutVersion", "WorkItemLayout") + b.HasOne("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", "WorkItemLayout") .WithMany() .HasForeignKey("WorkItemLayoutId") .OnDelete(DeleteBehavior.Cascade) @@ -961,33 +1059,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bones.Database.DbSets.AccountManagement.BonesUser", b => { + b.Navigation("AssetLayouts"); + b.Navigation("Assets"); b.Navigation("Projects"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetField", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetFields.AssetField", b => { b.Navigation("PossibleValues"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayout", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayout", b => { b.Navigation("Versions"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetLayouts.AssetLayoutVersion", b => { b.Navigation("Fields"); }); - modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.AssetVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.Asset", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.AssetManagement.Assets.AssetVersion", b => { b.Navigation("Values"); }); modelBuilder.Entity("Bones.Database.DbSets.OrganizationManagement.BonesOrganization", b => { + b.Navigation("AssetLayouts"); + b.Navigation("Assets"); b.Navigation("Projects"); @@ -1007,32 +1114,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.Queue", b => { - b.Navigation("Items"); + b.Navigation("WorkItems"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItem", b => - { - b.Navigation("Tags"); - - b.Navigation("Versions"); - }); - - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemField", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemFields.WorkItemField", b => { b.Navigation("PossibleValues"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayout", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayout", b => { b.Navigation("Versions"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayoutVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemLayouts.WorkItemLayoutVersion", b => { b.Navigation("Fields"); }); - modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItemVersion", b => + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItem", b => + { + b.Navigation("Tags"); + + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Bones.Database.DbSets.ProjectManagement.WorkItems.WorkItemVersion", b => { b.Navigation("Values"); }); diff --git a/Backend/Bones.Database/Operations/Setup/SetupDb/SetupDbHandler.cs b/Backend/Bones.Database/Operations/Setup/SetupDb/SetupDbHandler.cs index 95fe478..0c2c747 100644 --- a/Backend/Bones.Database/Operations/Setup/SetupDb/SetupDbHandler.cs +++ b/Backend/Bones.Database/Operations/Setup/SetupDb/SetupDbHandler.cs @@ -12,7 +12,7 @@ public async Task Handle(SetupDbCommand request, CancellationTo await dbContext.Database.MigrateAsync(cancellationToken); } - CommandResponse admin = await sender.Send(new SetupSystemAdminUserAndRoleCommand(), cancellationToken); + await sender.Send(new SetupSystemAdminUserAndRoleCommand(), cancellationToken); return CommandResponse.Pass(); } diff --git a/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs b/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs index 2fd62bc..222dbc4 100644 --- a/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs +++ b/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs @@ -35,7 +35,7 @@ public abstract class PipelineBehaviorBase : IPipelineBehav private readonly bool _debugLog = false; #endif - private Stopwatch? _stopwatch = null; + private Stopwatch? _stopwatch; /// /// This is called before MediatR sends the request to its handler, we pass the request along with next() diff --git a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs index 3e3f43f..81d5670 100644 --- a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs +++ b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs @@ -104,6 +104,36 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else if (status_ == 200) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); @@ -193,24 +223,44 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 401) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) + if (status_ == 403) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -291,24 +341,44 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 401) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) + if (status_ == 403) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -379,24 +449,44 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 401) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) + if (status_ == 403) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -461,6 +551,36 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else if (status_ == 200) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); @@ -532,24 +652,44 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) + if (status_ == 403) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -621,6 +761,36 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else if (status_ == 200) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); @@ -660,6 +830,214 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() } } + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Throws a BonesException to test the ApiExceptionHandler + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetBonesExceptionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + + // Operation Path: "Test/bones-exception" + urlBuilder_.Append("Test/bones-exception"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Throws a ForbiddenAccessException to test the ApiExceptionHandler + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetForbiddenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + + // Operation Path: "Test/forbidden" + urlBuilder_.Append("Test/forbidden"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) diff --git a/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs b/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs index 91dae72..f83446d 100644 --- a/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs +++ b/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs @@ -21,7 +21,7 @@ public override async Task GetAuthenticationStateAsync() new(BonesClaimTypes.User.DISPLAY_NAME, currentUser.DisplayName) ]; - AuthenticationState? authenticationState = new(new(new ClaimsIdentity(claims, authenticationType: nameof(BonesAuthenticationStateProvider)))); + AuthenticationState authenticationState = new(new(new ClaimsIdentity(claims, authenticationType: nameof(BonesAuthenticationStateProvider)))); return authenticationState; } diff --git a/Tests/Backend/Bones.Backend.UnitTests/Bones.Backend.UnitTests.csproj b/Tests/Backend/Bones.Backend.UnitTests/Bones.Backend.UnitTests.csproj index 3fa33a9..b55f3c0 100644 --- a/Tests/Backend/Bones.Backend.UnitTests/Bones.Backend.UnitTests.csproj +++ b/Tests/Backend/Bones.Backend.UnitTests/Bones.Backend.UnitTests.csproj @@ -26,11 +26,6 @@ - - - - - diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ConfirmEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ConfirmEmailTests.cs new file mode 100644 index 0000000..d905903 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ConfirmEmailTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class ConfirmEmailTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs new file mode 100644 index 0000000..a1d8de6 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class QueueConfirmationEmailTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs new file mode 100644 index 0000000..d58ac67 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class QueueForgotPasswordEmailTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueResendConfirmationEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueResendConfirmationEmailTests.cs new file mode 100644 index 0000000..b48d58c --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueResendConfirmationEmailTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class QueueResendConfirmationEmailTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs new file mode 100644 index 0000000..e4ddbec --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class RegisterUserTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ResetPasswordTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ResetPasswordTests.cs new file mode 100644 index 0000000..b951e49 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/ResetPasswordTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.AccountManagement; + +public class ResetPasswordTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/OrganizationManagement/UserHasOrganizationPermissionTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/OrganizationManagement/UserHasOrganizationPermissionTests.cs new file mode 100644 index 0000000..48dfd74 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/OrganizationManagement/UserHasOrganizationPermissionTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.OrganizationManagement; + +public class UserHasOrganizationPermissionTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/CreateInitiativeTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/CreateInitiativeTests.cs new file mode 100644 index 0000000..894f761 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/CreateInitiativeTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.ProjectManagement.Initiatives; + +public class CreateInitiativeTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/GetInitiativeByIdTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/GetInitiativeByIdTests.cs new file mode 100644 index 0000000..0b2d76f --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/GetInitiativeByIdTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.ProjectManagement.Initiatives; + +public class GetInitiativeByIdTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeByIdTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeByIdTests.cs new file mode 100644 index 0000000..85c009e --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeByIdTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.ProjectManagement.Initiatives; + +public class QueueDeleteInitiativeByIdTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/CreateProjectTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/CreateProjectTests.cs new file mode 100644 index 0000000..0e61ab8 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/CreateProjectTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.ProjectManagement.Projects; + +public class CreateProjectTests +{ + +} \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/UserHasProjectPermissionTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/UserHasProjectPermissionTests.cs new file mode 100644 index 0000000..8aa4a74 --- /dev/null +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/ProjectManagement/Projects/UserHasProjectPermissionTests.cs @@ -0,0 +1,6 @@ +namespace Bones.Backend.UnitTests.Features.ProjectManagement.Projects; + +public class UserHasProjectPermissionTests +{ + +} \ No newline at end of file