Skip to content

Commit

Permalink
Get My Invitations (#28)
Browse files Browse the repository at this point in the history
- added get my invitations endpoint, query and query handler
- added get my invitations endpoint tests
- fixed get team invitations
  • Loading branch information
skrasekmichael authored Feb 25, 2024
1 parent cd3c692 commit c674d77
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 16 deletions.
31 changes: 31 additions & 0 deletions src/TeamUp.Api/Endpoints/Invitations/GetMyInvitationsEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using MediatR;

using Microsoft.AspNetCore.Mvc;

using TeamUp.Api.Extensions;
using TeamUp.Application.Invitations.GetMyInvitations;
using TeamUp.Contracts.Invitations;

namespace TeamUp.Api.Endpoints.Invitations;

public sealed class GetMyInvitationsEndpoint : IEndpointGroup
{
public void MapEndpoints(RouteGroupBuilder group)
{
group.MapGet("/", GetTeamInvitationsAsync)
.Produces<List<InvitationResponse>>(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status401Unauthorized)
.WithName(nameof(GetMyInvitationsEndpoint))
.MapToApiVersion(1);
}

private async Task<IResult> GetTeamInvitationsAsync(
[FromServices] ISender sender,
HttpContext httpContext,
CancellationToken ct)
{
var query = new GetMyInvitationsQuery(httpContext.GetCurrentUserId());
var result = await sender.Send(query, ct);
return result.Match(TypedResults.Ok);
}
}
3 changes: 2 additions & 1 deletion src/TeamUp.Api/Endpoints/InvitationsEndpointGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public void MapEndpoints(RouteGroupBuilder group)
group.RequireAuthorization()
.MapEndpoint<InviteUserEndpoint>()
.MapEndpoint<GetTeamInvitationsEndpoint>()
.MapEndpoint<AcceptInvitationEndpoint>();
.MapEndpoint<AcceptInvitationEndpoint>()
.MapEndpoint<GetMyInvitationsEndpoint>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using TeamUp.Application.Abstractions;
using TeamUp.Common;
using TeamUp.Contracts.Invitations;
using TeamUp.Contracts.Users;

namespace TeamUp.Application.Invitations.GetMyInvitations;

public sealed record GetMyInvitationsQuery(UserId InitiatorId) : IQuery<Result<List<InvitationResponse>>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;

using TeamUp.Application.Abstractions;
using TeamUp.Common;
using TeamUp.Contracts.Invitations;

namespace TeamUp.Application.Invitations.GetMyInvitations;

internal sealed class GetMyInvitationsQueryHandler : IQueryHandler<GetMyInvitationsQuery, Result<List<InvitationResponse>>>
{
private readonly IAppQueryContext _appQueryContext;

public GetMyInvitationsQueryHandler(IAppQueryContext appQueryContext)
{
_appQueryContext = appQueryContext;
}

public async Task<Result<List<InvitationResponse>>> Handle(GetMyInvitationsQuery request, CancellationToken ct)
{
return await _appQueryContext.Invitations
.Where(invitation => invitation.RecipientId == request.InitiatorId)
.Select(invitation => new InvitationResponse
{
Id = invitation.Id,
TeamName = _appQueryContext.Teams.First(team => team.Id == invitation.TeamId).Name,
CreatedUtc = invitation.CreatedUtc
})
.ToListAsync(ct);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,13 @@ public async Task<Result<List<TeamInvitationResponse>>> Handle(GetTeamInvitation
.ThenAsync(team =>
{
return _appQueryContext.Invitations
.Select(invitation => new
{
invitation.TeamId,
invitation.CreatedUtc,
_appQueryContext.Users
.Select(user => new { user.Id, user.Email })
.First(user => user.Id == invitation.RecipientId).Email,
})
.Where(invitation => invitation.TeamId == request.TeamId)
.Select(invitation => new TeamInvitationResponse
{
Email = invitation.Email,
Id = invitation.Id,
Email = _appQueryContext.Users
.Select(user => new { user.Id, user.Email })
.First(user => user.Id == invitation.RecipientId).Email,
CreatedUtc = invitation.CreatedUtc
})
.ToListAsync(ct);
Expand Down
10 changes: 10 additions & 0 deletions src/TeamUp.Contracts/Invitations/InvitationResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using TeamUp.Contracts.Teams;

namespace TeamUp.Contracts.Invitations;

public sealed class InvitationResponse
{
public required InvitationId Id { get; init; }
public required string TeamName { get; init; }
public required DateTime CreatedUtc { get; init; }
}
2 changes: 1 addition & 1 deletion src/TeamUp.Contracts/Invitations/TeamInvitationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public sealed class TeamInvitationResponse
{
public required InvitationId Id { get; init; }
public required string Email { get; init; }

public required DateTime CreatedUtc { get; init; }
}
13 changes: 12 additions & 1 deletion tests/TeamUp.EndToEndTests/DataGenerators/InvitationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static Invitation GenerateInvitation(UserId userId, TeamId teamId, DateTi
.Generate();
}

public static List<Invitation> GenerateInvitations(TeamId teamId, DateTime createdUtc, List<User> users)
public static List<Invitation> GenerateTeamInvitations(TeamId teamId, DateTime createdUtc, List<User> users)
{
return users.Select(user =>
EmptyInvitation
Expand All @@ -35,6 +35,17 @@ public static List<Invitation> GenerateInvitations(TeamId teamId, DateTime creat
).ToList();
}

public static List<Invitation> GenerateUserInvitations(UserId userId, DateTime createdUtc, List<Team> teams)
{
return teams.Select(team =>
EmptyInvitation
.RuleForBackingField(i => i.RecipientId, userId)
.RuleForBackingField(i => i.TeamId, team.Id)
.RuleFor(i => i.CreatedUtc, createdUtc)
.Generate()
).ToList();
}

public sealed class InvalidInviteUserRequest : TheoryData<InvalidRequest<InviteUserRequest>>
{
public InvalidInviteUserRequest()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using TeamUp.Contracts.Invitations;

namespace TeamUp.EndToEndTests.EndpointTests.Invitations;

public sealed class GetMyInvitationsTests : BaseInvitationTests
{
public GetMyInvitationsTests(TeamApiWebApplicationFactory appFactory) : base(appFactory) { }

public const string URL = "/api/v1/invitations";

[Fact]
public async Task GetMyInvitations_Should_ReturnListOfInvitations()
{
//arrange
var owner = UserGenerator.ActivatedUser.Generate();
var initiatorUser = UserGenerator.ActivatedUser.Generate();
var members = UserGenerator.ActivatedUser.Generate(19);
var teams = new List<Team>
{
TeamGenerator.GenerateTeamWith(owner, members),
TeamGenerator.GenerateTeamWith(owner, members),
TeamGenerator.GenerateTeamWith(owner, members)
};

//need to remove milliseconds as there is slight shift when saving to database
var utcNow = new DateTime(DateTime.UtcNow.Ticks / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond, DateTimeKind.Utc);
var invitations = InvitationGenerator.GenerateUserInvitations(initiatorUser.Id, utcNow, teams);

await UseDbContextAsync(dbContext =>
{
dbContext.Users.AddRange([owner, initiatorUser]);
dbContext.Users.AddRange(members);
dbContext.Teams.AddRange(teams);
dbContext.Invitations.AddRange(invitations);
return dbContext.SaveChangesAsync();
});

Authenticate(initiatorUser);

//act
var response = await Client.GetAsync(URL);

//assert
response.Should().Be200Ok();

var userInvitations = await response.ReadFromJsonAsync<List<InvitationResponse>>();
invitations.Should().BeEquivalentTo(userInvitations, o => o.ExcludingMissingMembers());
}

[Fact]
public async Task GetMyInvitations_WhenNotInvited_Should_ReturnEmptyList()
{
//arrange
var owner = UserGenerator.ActivatedUser.Generate();
var initiatorUser = UserGenerator.ActivatedUser.Generate();
var members = UserGenerator.ActivatedUser.Generate(19);
var teams = new List<Team>
{
TeamGenerator.GenerateTeamWith(owner, members),
TeamGenerator.GenerateTeamWith(owner, members),
TeamGenerator.GenerateTeamWith(owner, members)
};

await UseDbContextAsync(dbContext =>
{
dbContext.Users.AddRange([owner, initiatorUser]);
dbContext.Users.AddRange(members);
dbContext.Teams.AddRange(teams);
return dbContext.SaveChangesAsync();
});

Authenticate(initiatorUser);

//act
var response = await Client.GetAsync(URL);

//assert
response.Should().Be200Ok();

var invitations = await response.ReadFromJsonAsync<List<InvitationResponse>>();
invitations.Should().BeEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public async Task GetTeamInvitations_AsCoordinatorOrHigher_Should_ReturnListOfIn
var members = UserGenerator.ActivatedUser.Generate(19);
var team = TeamGenerator.GenerateTeamWith(initiatorUser, teamRole, members);

//need to remove milliseconds as when saving to database, there is slight shift
//need to remove milliseconds as there is slight shift when saving to database
var utcNow = new DateTime(DateTime.UtcNow.Ticks / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond, DateTimeKind.Utc);
var invitations = InvitationGenerator.GenerateInvitations(team.Id, utcNow, members);
var invitations = InvitationGenerator.GenerateTeamInvitations(team.Id, utcNow, members);

await UseDbContextAsync(dbContext =>
{
Expand Down Expand Up @@ -53,7 +53,7 @@ public async Task GetTeamInvitations_AsMember_Should_ResultInForbidden()
var initiatorUser = UserGenerator.ActivatedUser.Generate();
var members = UserGenerator.ActivatedUser.Generate(19);
var team = TeamGenerator.GenerateTeamWith(initiatorUser, TeamRole.Member, members);
var invitations = InvitationGenerator.GenerateInvitations(team.Id, DateTime.UtcNow, members);
var invitations = InvitationGenerator.GenerateTeamInvitations(team.Id, DateTime.UtcNow, members);

await UseDbContextAsync(dbContext =>
{
Expand Down Expand Up @@ -84,7 +84,7 @@ public async Task GetTeamInvitations_WhenNotMemberOfTeam_Should_ResultInForbidde
var initiatorUser = UserGenerator.ActivatedUser.Generate();
var members = UserGenerator.ActivatedUser.Generate(19);
var team = TeamGenerator.GenerateTeamWith(owner, members);
var invitations = InvitationGenerator.GenerateInvitations(team.Id, DateTime.UtcNow, members);
var invitations = InvitationGenerator.GenerateTeamInvitations(team.Id, DateTime.UtcNow, members);

await UseDbContextAsync(dbContext =>
{
Expand Down

0 comments on commit c674d77

Please sign in to comment.