Skip to content

Commit

Permalink
Merge pull request #170 from PinguApps/112-create-phone-token
Browse files Browse the repository at this point in the history
Implemented create phone token
  • Loading branch information
pingu2k4 authored Sep 23, 2024
2 parents be164c4 + 5b9f3d9 commit 50c4f9d
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 11 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
```

## ⌛ Progress
<!-- ![49 / 291](https://progress-bar.dev/49/?scale=291&suffix=%20/%20288&width=500) -->
![Server & Client - 49 / 291](https://img.shields.io/badge/Server_&_Client-49%20%2F%20291-red?style=for-the-badge)
<!-- ![51 / 291](https://progress-bar.dev/51/?scale=291&suffix=%20/%20288&width=500) -->
![Server & Client - 51 / 291](https://img.shields.io/badge/Server_&_Client-51%20%2F%20291-red?style=for-the-badge)

<!-- ![9 / 201](https://progress-bar.dev/9/?scale=195&suffix=%20/%20201&width=300) -->
![Server - 9 / 201](https://img.shields.io/badge/Server-9%20%2F%20201-red?style=for-the-badge)
<!-- ![10 / 201](https://progress-bar.dev/10/?scale=201&suffix=%20/%20201&width=300) -->
![Server - 10 / 201](https://img.shields.io/badge/Server-10%20%2F%20201-red?style=for-the-badge)

<!-- ![40 / 93](https://progress-bar.dev/40/?scale=93&suffix=%20/%2093&width=300) -->
![Client - 40 / 90](https://img.shields.io/badge/Client-40%20%2F%2090-gold?style=for-the-badge)
<!-- ![41 / 93](https://progress-bar.dev/41/?scale=93&suffix=%20/%2093&width=300) -->
![Client - 41 / 90](https://img.shields.io/badge/Client-41%20%2F%2090-gold?style=for-the-badge)

### 🔑 Key
| Icon | Definition |
Expand All @@ -155,8 +155,8 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
|| There is currently no intention to implement the endpoint for the given SDK type (client or server) |

### Account
<!-- ![49 / 55](https://progress-bar.dev/49/?scale=55&suffix=%20/%2052&width=120) -->
![Account - 49 / 55](https://img.shields.io/badge/Account-49%20%2F%2055-forestgreen?style=for-the-badge)
<!-- ![51 / 55](https://progress-bar.dev/51/?scale=55&suffix=%20/%2052&width=120) -->
![Account - 51 / 55](https://img.shields.io/badge/Account-51%20%2F%2055-forestgreen?style=for-the-badge)

| Endpoint | Client | Server | Notes |
|:-:|:-:|:-:|:-:|
Expand Down Expand Up @@ -190,7 +190,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
| [Create Email Password Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#createEmailPasswordSession) ||| |
| [Update Magic URL Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#updateMagicURLSession) ||| |
| [Create OAuth2 Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#createOAuth2Session) ||| |
| [Update Phone Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#updatePhoneSession) | | | |
| [Update Phone Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#updatePhoneSession) | | | |
| [Create Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#createSession) ||| |
| [Get Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#getSession) ||| |
| [Update Session](https://appwrite.io/docs/references/1.6.x/client-rest/account#updateSession) ||| |
Expand Down
17 changes: 17 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/AccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,4 +697,21 @@ public async Task<AppwriteResult> DeleteIdentity(DeleteIdentityRequest request)
return e.GetExceptionResponse();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<Token>> CreatePhoneToken(CreatePhoneTokenRequest request)
{
try
{
request.Validate(true);

var result = await _accountApi.CreatePhoneToken(request);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Token>();
}
}
}
10 changes: 10 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,18 @@ public interface IAccountClient

/// <summary>
/// Delete an identity by its unique ID
/// <para><see href="https://appwrite.io/docs/references/1.6.x/client-rest/account#deleteIdentity">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>code 204 ofr success</returns>
Task<AppwriteResult> DeleteIdentity(DeleteIdentityRequest request);

/// <summary>
/// <para>Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to <see cref="CreateSession(CreateSessionRequest)"/> to complete the login process. The secret sent to the user's phone is valid for 15 minutes.</para>
/// <para>A user is limited to 10 active sessions at a time by default. <see href="https://appwrite.io/docs/authentication-security#limits">Learn more about session limits</see>.</para>
/// <para><see href="https://appwrite.io/docs/references/1.6.x/client-rest/account#createPhoneToken">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>The token</returns>
Task<AppwriteResult<Token>> CreatePhoneToken(CreatePhoneTokenRequest request);
}
3 changes: 3 additions & 0 deletions src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,7 @@ internal interface IAccountApi : IBaseApi

[Delete("/account/identities/{identityId}")]
Task<IApiResponse> DeleteIdentity([Header("x-appwrite-session")] string session, string identityId);

[Post("/account/tokens/phone")]
Task<IApiResponse<Token>> CreatePhoneToken(CreatePhoneTokenRequest request);
}
5 changes: 3 additions & 2 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public async Task Run(string[] args)
{
_client.SetSession(_session);

var response = await _client.Account.DeleteIdentity(new DeleteIdentityRequest
var response = await _server.Account.CreatePhoneToken(new CreatePhoneTokenRequest
{
IdentityId = "my identity id"
UserId = "664aac1a00113f82e620",
PhoneNumber = "+447500112374"
});

Console.WriteLine(response.Result.Match(
Expand Down
3 changes: 3 additions & 0 deletions src/PinguApps.Appwrite.Server/Internals/IAccountApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ internal interface IAccountApi : IBaseApi

[Put("/account/sessions/magic-url")]
Task<IApiResponse<Session>> UpdateMagicUrlSession(UpdateMagicUrlSessionRequest request);

[Post("/account/tokens/phone")]
Task<IApiResponse<Token>> CreatePhoneToken(CreatePhoneTokenRequest request);
}
17 changes: 17 additions & 0 deletions src/PinguApps.Appwrite.Server/Servers/AccountServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,21 @@ public AppwriteResult<CreateOauth2Session> CreateOauth2Session(CreateOauth2Sessi
return e.GetExceptionResponse<CreateOauth2Session>();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<Token>> CreatePhoneToken(CreatePhoneTokenRequest request)
{
try
{
request.Validate(true);

var result = await _accountApi.CreatePhoneToken(request);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Token>();
}
}
}
9 changes: 9 additions & 0 deletions src/PinguApps.Appwrite.Server/Servers/IAccountServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,13 @@ public interface IAccountServer
/// <param name="request">The request content</param>
/// <returns>The CreateOauth2Session object</returns>
AppwriteResult<CreateOauth2Session> CreateOauth2Session(CreateOauth2SessionRequest request);

/// <summary>
/// <para>Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to <see cref="CreateSession(CreateSessionRequest)"/> to complete the login process. The secret sent to the user's phone is valid for 15 minutes.</para>
/// <para>A user is limited to 10 active sessions at a time by default. <see href="https://appwrite.io/docs/authentication-security#limits">Learn more about session limits</see>.</para>
/// <para><see href="https://appwrite.io/docs/references/1.6.x/server-rest/account#createPhoneToken">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>The token</returns>
Task<AppwriteResult<Token>> CreatePhoneToken(CreatePhoneTokenRequest request);
}
22 changes: 22 additions & 0 deletions src/PinguApps.Appwrite.Shared/Requests/CreatePhoneTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
using PinguApps.Appwrite.Shared.Requests.Validators;

namespace PinguApps.Appwrite.Shared.Requests;

/// <summary>
/// The request to create a phone token
/// </summary>
public class CreatePhoneTokenRequest : BaseRequest<CreatePhoneTokenRequest, CreatePhoneTokenRequestValidator>
{
/// <summary>
/// Unique Id. Choose a custom ID or generate a random ID with <see cref="Utils.IdUtils.GenerateUniqueId(int)"/>. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.
/// </summary>
[JsonPropertyName("userId")]
public string UserId { get; set; } = string.Empty;

/// <summary>
/// Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212
/// </summary>
[JsonPropertyName("phone")]
public string PhoneNumber { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FluentValidation;

namespace PinguApps.Appwrite.Shared.Requests.Validators;
public class CreatePhoneTokenRequestValidator : AbstractValidator<CreatePhoneTokenRequest>
{
public CreatePhoneTokenRequestValidator()
{
RuleFor(request => request.UserId)
.NotEmpty().WithMessage("User ID must not be empty.")
.Matches("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$").WithMessage("User ID must be alphanumeric and can include periods, hyphens, and underscores. It cannot start with a special character and must be at most 36 characters long.");

RuleFor(request => request.PhoneNumber)
.NotEmpty().WithMessage("Phone number must not be empty.")
.Matches(@"^\+\d{1,15}$").WithMessage("Phone number must start with a '+' and include the country code, followed by up to 15 digits.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Net;
using PinguApps.Appwrite.Shared.Requests;
using PinguApps.Appwrite.Shared.Tests;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Client.Tests.Clients.Account;
public partial class AccountClientTests
{
[Fact]
public async Task CreatePhoneToken_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Respond(Constants.AppJson, Constants.TokenResponse);

// Act
var result = await _appwriteClient.Account.CreatePhoneToken(request);

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

[Fact]
public async Task CreatePhoneToken_ShouldHandleException_WhenApiCallFails()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError);

// Act
var result = await _appwriteClient.Account.CreatePhoneToken(request);

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

[Fact]
public async Task CreatePhoneToken_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Throw(new HttpRequestException("An error occurred"));

// Act
var result = await _appwriteClient.Account.CreatePhoneToken(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,77 @@
using System.Net;
using PinguApps.Appwrite.Shared.Requests;
using PinguApps.Appwrite.Shared.Tests;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Server.Tests.Servers.Account;
public partial class AccountServerTests
{
[Fact]
public async Task CreatePhoneToken_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Respond(Constants.AppJson, Constants.TokenResponse);

// Act
var result = await _appwriteServer.Account.CreatePhoneToken(request);

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

[Fact]
public async Task CreatePhoneToken_ShouldHandleException_WhenApiCallFails()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError);

// Act
var result = await _appwriteServer.Account.CreatePhoneToken(request);

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

[Fact]
public async Task CreatePhoneToken_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
var request = new CreatePhoneTokenRequest()
{
UserId = "123456",
PhoneNumber = "+16175551212"
};

_mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/tokens/phone")
.ExpectedHeaders()
.WithJsonContent(request)
.Throw(new HttpRequestException("An error occurred"));

// Act
var result = await _appwriteServer.Account.CreatePhoneToken(request);

// Assert
Assert.False(result.Success);
Assert.True(result.IsInternalError);
Assert.Equal("An error occurred", result.Result.AsT2.Message);
}
}
Loading

0 comments on commit 50c4f9d

Please sign in to comment.