diff --git a/.github/actions/verify-pact-contract/action.yaml b/.github/actions/verify-pact-contract/action.yaml index de66113..8ad264a 100644 --- a/.github/actions/verify-pact-contract/action.yaml +++ b/.github/actions/verify-pact-contract/action.yaml @@ -10,7 +10,7 @@ inputs: consumer_tags: required: false -verify-contract: +runs: using: "composite" steps: - name: Checkout code @@ -22,6 +22,7 @@ verify-contract: dotnet-version: '9.0.x' - name: Verify consumer contract against provider + shell: bash run: | echo "Verifying pact..." cd UserMicroservice.Tests diff --git a/.github/workflows/group-ms-workflow.yaml b/.github/workflows/group-ms-workflow.yaml index ab61520..974032f 100644 --- a/.github/workflows/group-ms-workflow.yaml +++ b/.github/workflows/group-ms-workflow.yaml @@ -5,6 +5,9 @@ on: paths: - 'GroupMicroservice/**' - 'GroupMicroservice.Tests/**' + - '.github/workflows/group-ms-workflow.yaml' + - '.github/actions/check-schema/**' + - '.github/actions/check-consumer-contract/**' workflow_call: secrets: PACT_BROKER: diff --git a/.github/workflows/user-ms-workflow.yaml b/.github/workflows/user-ms-workflow.yaml index 7f2ae48..28565aa 100644 --- a/.github/workflows/user-ms-workflow.yaml +++ b/.github/workflows/user-ms-workflow.yaml @@ -5,6 +5,9 @@ on: paths: - 'UserMicroservice/**' - 'UserMicroservice.Tests/**' + - '.github/workflows/user-ms-workflow.yaml' + - '.github/actions/check-schema/**' + - '.github/actions/verify-pact-contract/**' workflow_call: secrets: PACT_BROKER: @@ -14,16 +17,11 @@ on: PACT_BROKER_PASSWORD: required: true workflow_dispatch: - -# inputs: -# consumer: -# required: true -# consumerVersion: -# required: true -# consumerVersionTags: -# required: false -# pactUrl: -# required: true + inputs: + group-ms-branch: + description: 'Branch name in Group Microservice' + required: true + default: 'main' jobs: check-openapi-schema: @@ -43,10 +41,22 @@ jobs: name: Verify consumer contract needs: check-openapi-schema steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract Group MS branch from PR description + if: ${{ github.event_name == 'pull_request' }} + id: extract-branch + run: | + echo "PR_BODY=${{ github.event.pull_request.body }}" >> $GITHUB_ENV + GROUP_MS_BRANCH=$(echo "${{ github.event.pull_request.body }}" | grep -oP '(?<=GroupMS: )[^\s]+') + echo "group-ms-branch=${GROUP_MS_BRANCH:-main}" >> $GITHUB_ENV + echo "The branch name in Group Microservice is: $GROUP_MS_BRANCH" + - name: Verify consumer contract uses: ./.github/actions/verify-pact-contract with: pact_broker: ${{ secrets.PACT_BROKER }} pact_broker_username: ${{ secrets.PACT_BROKER_USERNAME }} pact_broker_password: ${{ secrets.PACT_BROKER_PASSWORD }} - \ No newline at end of file + consumer_tags: ${{ env.group-ms-branch || inputs.group-ms-branch }} diff --git a/Docs/check-schema-diagram.drawio b/Docs/check-schema-diagram.drawio new file mode 100644 index 0000000..54f14aa --- /dev/null +++ b/Docs/check-schema-diagram.drawio @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Docs/workflow-diagram.drawio b/Docs/workflow-diagram.drawio new file mode 100644 index 0000000..890c340 --- /dev/null +++ b/Docs/workflow-diagram.drawio @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UserMicroservice.Tests/Setup/MockUserRepository.cs b/UserMicroservice.Tests/Setup/MockUserRepository.cs index f6cbd41..18ca4a7 100644 --- a/UserMicroservice.Tests/Setup/MockUserRepository.cs +++ b/UserMicroservice.Tests/Setup/MockUserRepository.cs @@ -48,4 +48,10 @@ public Task DeleteUserAsync(UserEntity user) } return Task.CompletedTask; } + + public Task> GetUsersByIdsAsync(List userIds) + { + var users = _users.Where(u => userIds.Contains(u.Id)).ToList(); + return Task.FromResult(users); + } } \ No newline at end of file diff --git a/UserMicroservice.Tests/Setup/ProviderStateMiddleware.cs b/UserMicroservice.Tests/Setup/ProviderStateMiddleware.cs index 6535de3..888aeb3 100644 --- a/UserMicroservice.Tests/Setup/ProviderStateMiddleware.cs +++ b/UserMicroservice.Tests/Setup/ProviderStateMiddleware.cs @@ -24,6 +24,7 @@ public class ProviderStateMiddleware /// Initialises a new instance of the class. /// /// Next request delegate + /// public ProviderStateMiddleware(RequestDelegate next, IUserRepository userRepository) { _next = next; @@ -31,28 +32,14 @@ public ProviderStateMiddleware(RequestDelegate next, IUserRepository userReposit _providerStates = new Dictionary, HttpContext, Task>> { - ["a user with id {id} exists"] = EnsureEventExistsAsync, - ["a user with id {id} does not exist"] = async (parameters, httpContext) => - { - var id = parameters["id"].ToString(); - var user = await userRepository.GetUserByIdAsync(Guid.Parse(id)); - if (user == null) - { - return; - } - - await userRepository.DeleteUserAsync(user); - } + ["a user with id {id} exists"] = EnsureUserExistsAsync, + ["a user with id {id} does not exist"] = EnsureUserDoesNotExistAsync, + ["users with the specified IDs exist"] = EnsureValidBatchRequestAsync, + ["users with the specified IDs do not exist"] = EnsureInvalidBatchRequestAsync }; } - /// - /// Ensure an event exists - /// - /// Event parameters - /// - /// Awaitable - private async Task EnsureEventExistsAsync(IDictionary parameters, HttpContext httpContext) + private async Task EnsureUserExistsAsync(IDictionary parameters, HttpContext httpContext) { var id = Guid.Parse(parameters["id"].ToString()); var existingUser = await _userRepository.GetUserByIdAsync(id); @@ -68,6 +55,52 @@ await _userRepository.CreateUserAsync(new UserEntity }); } } + + private async Task EnsureUserDoesNotExistAsync(IDictionary parameters, HttpContext httpContext) + { + var id = Guid.Parse(parameters["id"].ToString()); + var existingUser = await _userRepository.GetUserByIdAsync(id); + if (existingUser != null) + { + await _userRepository.DeleteUserAsync(existingUser); + } + } + + private async Task EnsureValidBatchRequestAsync(IDictionary arg1, HttpContext arg2) + { + var userIds = arg1["ids"].ToString().Split(","); + foreach (var user in userIds) + { + var id = Guid.Parse(user); + var existingUser = await _userRepository.GetUserByIdAsync(id); + if (existingUser == null) + { + await _userRepository.CreateUserAsync(new UserEntity + { + Id = id, + Email = $"{id.ToString()}@app.com", + Nickname = id.ToString(), + CreatedAt = new DateTime(2009, 7, 27, 0, 0, 0), + Password = "password" + }); + } + } + } + + private async Task EnsureInvalidBatchRequestAsync(IDictionary arg1, HttpContext arg2) + { + var userIds = arg1["ids"].ToString().Split(","); + + foreach (var user in userIds) + { + var id = Guid.Parse(user); + var existingUser = await _userRepository.GetUserByIdAsync(id); + if (existingUser != null) + { + await _userRepository.DeleteUserAsync(existingUser); + } + } + } /// /// Handle the request diff --git a/UserMicroservice/Application/IUserService.cs b/UserMicroservice/Application/IUserService.cs index 2493c8e..e6ec2f9 100644 --- a/UserMicroservice/Application/IUserService.cs +++ b/UserMicroservice/Application/IUserService.cs @@ -9,4 +9,5 @@ public interface IUserService Task CreateUserAsync(CreateUserDto user); Task UpdateUserAsync(UpdateUserDto user); Task DeleteUserAsync(Guid userId); + Task> GetUsersAsync(List userIds); } \ No newline at end of file diff --git a/UserMicroservice/Application/UserService.cs b/UserMicroservice/Application/UserService.cs index 282cfc0..495aa27 100644 --- a/UserMicroservice/Application/UserService.cs +++ b/UserMicroservice/Application/UserService.cs @@ -56,6 +56,17 @@ public async Task DeleteUserAsync(Guid userId) await userRepository.DeleteUserAsync(existingUser); } + public async Task> GetUsersAsync(List userIds) + { + var users = await userRepository.GetUsersByIdsAsync(userIds); + if (users == null || users.Count == 0) + { + throw new NotFoundException("No users found for the provided IDs."); + } + + return users.Select(user => user.ToGetUserDto()).ToList(); + } + private async Task GetUserEntityByIdAsync(Guid userId) { var user = await userRepository.GetUserByIdAsync(userId); diff --git a/UserMicroservice/Infrastructure/IUserRepository.cs b/UserMicroservice/Infrastructure/IUserRepository.cs index 49a3225..830588d 100644 --- a/UserMicroservice/Infrastructure/IUserRepository.cs +++ b/UserMicroservice/Infrastructure/IUserRepository.cs @@ -9,4 +9,5 @@ public interface IUserRepository Task CreateUserAsync(UserEntity user); Task UpdateUserAsync(UserEntity user); Task DeleteUserAsync(UserEntity user); + Task> GetUsersByIdsAsync(List userIds); } \ No newline at end of file diff --git a/UserMicroservice/Infrastructure/UserRepository.cs b/UserMicroservice/Infrastructure/UserRepository.cs index cc44e8b..25b2ced 100644 --- a/UserMicroservice/Infrastructure/UserRepository.cs +++ b/UserMicroservice/Infrastructure/UserRepository.cs @@ -34,4 +34,11 @@ public async Task DeleteUserAsync(UserEntity user) context.Users.Remove(user); await context.SaveChangesAsync(); } + + public async Task> GetUsersByIdsAsync(List userIds) + { + return await context.Users + .Where(u => userIds.Contains(u.Id)) + .ToListAsync(); + } } \ No newline at end of file diff --git a/UserMicroservice/Presentation/Apis/UserApi.cs b/UserMicroservice/Presentation/Apis/UserApi.cs index 2bb0a16..48ff635 100644 --- a/UserMicroservice/Presentation/Apis/UserApi.cs +++ b/UserMicroservice/Presentation/Apis/UserApi.cs @@ -25,7 +25,10 @@ public static RouteGroupBuilder AddUserApi(this IEndpointRouteBuilder app) api.MapGet("/{userId:guid}", GetUserByIdAsync) .WithName("GetUserById"); - + + api.MapPost("/batch", GetUsersAsync) + .WithName("GetUsers"); + return api; } @@ -60,4 +63,12 @@ private static async Task, ProblemHttpResult>> GetUserByI var user = await userService.GetUserByIdAsync(userId); return TypedResults.Ok(user); } + + private static async Task>, ProblemHttpResult>> GetUsersAsync( + [FromBody] List userIds, + [FromServices] IUserService userService) + { + var users = await userService.GetUsersAsync(userIds); + return TypedResults.Ok(users); + } } \ No newline at end of file diff --git a/UserMicroservice/openapi-schema.json b/UserMicroservice/openapi-schema.json index a1ac03c..1975cfb 100644 --- a/UserMicroservice/openapi-schema.json +++ b/UserMicroservice/openapi-schema.json @@ -115,6 +115,43 @@ } } } + }, + "/api/v1/user/batch": { + "post": { + "tags": [ + "User" + ], + "operationId": "GetUsers", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GetUserDto" + } + } + } + } + } + } + } } }, "components": {