Skip to content

Commit

Permalink
Merge pull request #332 from PinguApps/309-create-team-membership
Browse files Browse the repository at this point in the history
Implemented create team membership
  • Loading branch information
pingu2k4 authored Oct 19, 2024
2 parents f81ed71 + 42440fa commit 9a765e5
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 21 deletions.
1 change: 1 addition & 0 deletions .korbitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/PinguApps.Appwrite.Playground/*
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(

## ⌛ Progress
<!-- `red` for first third, `gold` for second third, `forestgreen` for final third, `blue` for 100% -->
![Server & Client - 112 / 318](https://img.shields.io/badge/Server_&_Client-112%20%2F%20318-gold?style=for-the-badge)
![Server & Client - 114 / 318](https://img.shields.io/badge/Server_&_Client-114%20%2F%20318-gold?style=for-the-badge)

![Server - 59 / 225](https://img.shields.io/badge/Server-59%20%2F%20225-red?style=for-the-badge)
![Server - 60 / 225](https://img.shields.io/badge/Server-60%20%2F%20225-red?style=for-the-badge)

![Client - 53 / 93](https://img.shields.io/badge/Client-53%20%2F%2093-gold?style=for-the-badge)
![Client - 54 / 93](https://img.shields.io/badge/Client-54%20%2F%2093-gold?style=for-the-badge)

### 🔑 Key
| Icon | Definition |
Expand Down Expand Up @@ -256,7 +256,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
| [Update Phone Verification](https://appwrite.io/docs/references/1.6.x/server-rest/users#updatePhoneVerification) |||

### Teams
![Teams - 12 / 26](https://img.shields.io/badge/Teams-12%20%2F%2026-gold?style=for-the-badge)
![Teams - 14 / 26](https://img.shields.io/badge/Teams-14%20%2F%2026-gold?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -266,7 +266,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
| [Update Name](https://appwrite.io/docs/references/1.6.x/client-rest/teams#updateName) |||
| [Delete Team](https://appwrite.io/docs/references/1.6.x/client-rest/teams#delete) |||
| [List Team Memberships](https://appwrite.io/docs/references/1.6.x/client-rest/teams#listMemberships) |||
| [Create Team Membership](https://appwrite.io/docs/references/1.6.x/client-rest/teams#createMembership) | | |
| [Create Team Membership](https://appwrite.io/docs/references/1.6.x/client-rest/teams#createMembership) | | |
| [Get Team Membership](https://appwrite.io/docs/references/1.6.x/client-rest/teams#getMembership) |||
| [Update Membership](https://appwrite.io/docs/references/1.6.x/client-rest/teams#updateMembership) |||
| [Delete Team Membership](https://appwrite.io/docs/references/1.6.x/client-rest/teams#deleteMembership) |||
Expand Down
11 changes: 10 additions & 1 deletion src/PinguApps.Appwrite.Client/Clients/ITeamsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,16 @@ public interface ITeamsClient
/// <param name="request">The request content</param>
/// <returns>The memberships list</returns>
Task<AppwriteResult<MembershipsList>> ListTeamMemberships(ListTeamMembershipsRequest request);
[Obsolete("This method hasn't yet been implemented!")]

/// <summary>
/// <para>Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.</para>
/// <para>You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.</para>
/// <para>Use the url parameter to redirect the user from the invitation email to your app. After the user is redirected, use <see cref="UpdateTeamMembershipStatus(UpdateTeamMembershipStatusRequest)"/> to allow the user to accept the invitation to the team.</para>
/// <para>Please note that to avoid a <see href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md">Redirect Attack</see> Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.</para>
/// <para><see href="https://appwrite.io/docs/references/1.6.x/client-rest/teams#createMembership">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>The membership</returns>
Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request);
[Obsolete("This method hasn't yet been implemented!")]
Task<AppwriteResult> DeleteTeamMembership(DeleteTeamMembershipRequest request);
Expand Down
17 changes: 15 additions & 2 deletions src/PinguApps.Appwrite.Client/Clients/TeamsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,22 @@ public async Task<AppwriteResult<MembershipsList>> ListTeamMemberships(ListTeamM
}
}

[ExcludeFromCodeCoverage]
/// <inheritdoc/>
public Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request) => throw new NotImplementedException();
public async Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request)
{
try
{
request.Validate(true);

var result = await _teamsApi.CreateTeamMembership(GetCurrentSessionOrThrow(), request.TeamId, request);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Membership>();
}
}

[ExcludeFromCodeCoverage]
/// <inheritdoc/>
Expand Down
19 changes: 11 additions & 8 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ public async Task Run(string[] args)
{
_client.SetSession(_session);

var request = new ListTeamMembershipsRequest()
var request = new CreateTeamMembershipRequest()
{
TeamId = "67142b78001c379958cb"
TeamId = "67142b78001c379958cb",
Email = "[email protected]",
Url = "https://localhost:1234/acceptTeamInvite",
Name = "Your Name"
};

var clientResponse = await _client.Teams.ListTeamMemberships(request);
//var clientResponse = await _client.Teams.CreateTeamMembership(request);

Console.WriteLine(clientResponse.Result.Match(
result => result.ToString(),
appwriteError => appwriteError.Message,
internalError => internalError.Message));
//Console.WriteLine(clientResponse.Result.Match(
// result => result.ToString(),
// appwriteError => appwriteError.Message,
// internalError => internalError.Message));

Console.WriteLine("############################################################################");

var serverResponse = await _server.Teams.ListTeamMemberships(request);
var serverResponse = await _server.Teams.CreateTeamMembership(request);

Console.WriteLine(serverResponse.Result.Match(
result => result.ToString(),
Expand Down
11 changes: 10 additions & 1 deletion src/PinguApps.Appwrite.Server/Clients/ITeamsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,16 @@ public interface ITeamsClient
/// <param name="request">The request content</param>
/// <returns>The memberships list</returns>
Task<AppwriteResult<MembershipsList>> ListTeamMemberships(ListTeamMembershipsRequest request);
[Obsolete("This method hasn't yet been implemented!")]

/// <summary>
/// <para>Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.</para>
/// <para>You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.</para>
/// <para>Use the url parameter to redirect the user from the invitation email to your app. After the user is redirected, use <see cref="UpdateTeamMembershipStatus(UpdateTeamMembershipStatusRequest)"/> to allow the user to accept the invitation to the team.</para>
/// <para>Please note that to avoid a <see href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md">Redirect Attack</see> Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.</para>
/// <para><see href="https://appwrite.io/docs/references/1.6.x/server-rest/teams#createMembership">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>The membership</returns>
Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request);
[Obsolete("This method hasn't yet been implemented!")]
Task<AppwriteResult> DeleteTeamMembership(DeleteTeamMembershipRequest request);
Expand Down
17 changes: 15 additions & 2 deletions src/PinguApps.Appwrite.Server/Clients/TeamsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,22 @@ public async Task<AppwriteResult<MembershipsList>> ListTeamMemberships(ListTeamM
}
}

[ExcludeFromCodeCoverage]
/// <inheritdoc/>
public Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request) => throw new NotImplementedException();
public async Task<AppwriteResult<Membership>> CreateTeamMembership(CreateTeamMembershipRequest request)
{
try
{
request.Validate(true);

var result = await _teamsApi.CreateTeamMembership(request.TeamId, request);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Membership>();
}
}

[ExcludeFromCodeCoverage]
/// <inheritdoc/>
Expand Down
3 changes: 2 additions & 1 deletion src/PinguApps.Appwrite.Shared/Responses/Membership.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using PinguApps.Appwrite.Shared.Converters;

namespace PinguApps.Appwrite.Shared.Responses;

Expand Down Expand Up @@ -30,7 +31,7 @@ public record Membership(
[property: JsonPropertyName("teamId")] string TeamId,
[property: JsonPropertyName("teamName")] string TeamName,
[property: JsonPropertyName("invited")] DateTime Invited,
[property: JsonPropertyName("joined")] DateTime Joined,
[property: JsonPropertyName("joined"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? Joined,
[property: JsonPropertyName("confirm")] bool Confirm,
[property: JsonPropertyName("mfa")] bool Mfa,
[property: JsonPropertyName("roles")] List<string> Roles
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Net;
using PinguApps.Appwrite.Client.Clients;
using PinguApps.Appwrite.Shared.Requests.Teams;
using PinguApps.Appwrite.Shared.Tests;
using PinguApps.Appwrite.Shared.Utils;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Client.Tests.Clients.Teams;
public partial class TeamsClientTests
{
[Fact]
public async Task CreateTeamMembership_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders(true)
.WithJsonContent(request, _jsonSerializerOptions)
.Respond(TestConstants.AppJson, TestConstants.MembershipResponse);

_appwriteClient.SetSession(TestConstants.Session);

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.True(result.Success);
}

[Fact]
public async Task CreateTeamMembership_ShouldReturnError_WhenSessionIsNull()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.True(result.IsError);
Assert.True(result.IsInternalError);
Assert.Equal(ISessionAware.SessionExceptionMessage, result.Result.AsT2.Message);
}

[Fact]
public async Task CreateTeamMembership_ShouldHandleException_WhenApiCallFails()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders(true)
.WithJsonContent(request, _jsonSerializerOptions)
.Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError);

_appwriteClient.SetSession(TestConstants.Session);

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.True(result.IsError);
Assert.True(result.IsAppwriteError);
}

[Fact]
public async Task CreateTeamMembership_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders(true)
.WithJsonContent(request, _jsonSerializerOptions)
.Throw(new HttpRequestException("An error occurred"));

_appwriteClient.SetSession(TestConstants.Session);

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.False(result.Success);
Assert.True(result.IsInternalError);
Assert.Equal("An error occurred", result.Result.AsT2.Message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Net;
using PinguApps.Appwrite.Shared.Requests.Teams;
using PinguApps.Appwrite.Shared.Tests;
using PinguApps.Appwrite.Shared.Utils;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Server.Tests.Clients.Teams;
public partial class TeamsClientTests
{
[Fact]
public async Task CreateTeamMembership_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders()
.WithJsonContent(request, _jsonSerializerOptions)
.Respond(TestConstants.AppJson, TestConstants.MembershipResponse);

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.True(result.Success);
}

[Fact]
public async Task CreateTeamMembership_ShouldHandleException_WhenApiCallFails()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders()
.WithJsonContent(request, _jsonSerializerOptions)
.Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError);

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.True(result.IsError);
Assert.True(result.IsAppwriteError);
}

[Fact]
public async Task CreateTeamMembership_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
var request = new CreateTeamMembershipRequest
{
TeamId = IdUtils.GenerateUniqueId(),
Email = "[email protected]",
Roles = ["role1", "role2"]
};

_mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/teams/{request.TeamId}/memberships")
.ExpectedHeaders()
.WithJsonContent(request, _jsonSerializerOptions)
.Throw(new HttpRequestException("An error occurred"));

// Act
var result = await _appwriteClient.Teams.CreateTeamMembership(request);

// Assert
Assert.False(result.Success);
Assert.True(result.IsInternalError);
Assert.Equal("An error occurred", result.Result.AsT2.Message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public void CanBeDeserialized_FromJson()
Assert.Equal("5e5ea5c16897e", membership.TeamId);
Assert.Equal("VIP", membership.TeamName);
Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), membership.Invited.ToUniversalTime());
Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), membership.Joined.ToUniversalTime());
Assert.NotNull(membership.Joined);
Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), membership.Joined.Value.ToUniversalTime());
Assert.False(membership.Confirm);
Assert.False(membership.Mfa);
Assert.Single(membership.Roles);
Expand Down
20 changes: 20 additions & 0 deletions tests/PinguApps.Appwrite.Shared.Tests/TestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,26 @@ public static class TestConstants
}
""";

public const string MembershipResponse = """
{
"$id": "5e5ea5c16897e",
"$createdAt": "2020-10-15T06:38:00.000+00:00",
"$updatedAt": "2020-10-15T06:38:00.000+00:00",
"userId": "5e5ea5c16897e",
"userName": "John Doe",
"userEmail": "[email protected]",
"teamId": "5e5ea5c16897e",
"teamName": "VIP",
"invited": "2020-10-15T06:38:00.000+00:00",
"joined": "2020-10-15T06:38:00.000+00:00",
"confirm": false,
"mfa": false,
"roles": [
"owner"
]
}
""";

public const string TargetListResponse = """
{
"total": 5,
Expand Down

0 comments on commit 9a765e5

Please sign in to comment.