Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update session implementation #78

Merged
merged 4 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(

## ⌛ Progress
### Server & Client
![13 / 288](https://progress-bar.dev/13/?scale=288&suffix=%20/%20288&width=500)
![14 / 288](https://progress-bar.dev/14/?scale=288&suffix=%20/%20288&width=500)
### Server Only
![2 / 195](https://progress-bar.dev/2/?scale=195&suffix=%20/%20195&width=300)
### Client Only
![11 / 93](https://progress-bar.dev/11/?scale=93&suffix=%20/%2093&width=300)
![12 / 93](https://progress-bar.dev/12/?scale=93&suffix=%20/%2093&width=300)

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

### Account
![13 / 52](https://progress-bar.dev/13/?scale=52&suffix=%20/%2052&width=120)
![14 / 52](https://progress-bar.dev/14/?scale=52&suffix=%20/%2052&width=120)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -190,7 +190,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
| [Update Phone Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#updatePhoneSession) | ⬛ | ❌ |
| [Create Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createSession) | ✅ | ❌ |
| [Get Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#getSession) | ✅ | ❌ |
| [Update Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateSession) | | ❌ |
| [Update Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateSession) | | ❌ |
| [Delete Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteSession) | ⬛ | ❌ |
| [Update Status](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateStatus) | ⬛ | ❌ |
| [Create Push Target](https://appwrite.io/docs/references/1.5.x/client-rest/account#createPushTarget) | ⬛ | ❌ |
Expand Down
15 changes: 15 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/AccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,19 @@ public async Task<AppwriteResult<Session>> GetSession(string sessionId = "curren
return e.GetExceptionResponse<Session>();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<Session>> UpdateSession(string sessionId = "current")
{
try
{
var result = await _accountApi.UpdateSession(Session, sessionId);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Session>();
}
}
}
8 changes: 8 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,12 @@ public interface IAccountClient
/// <param name="sessionId">Session ID. Use the string 'current' to get the current device session</param>
/// <returns>The session</returns>
Task<AppwriteResult<Session>> GetSession(string sessionId = "current");

/// <summary>
/// Use this endpoint to extend a session's length. Extending a session is useful when session expiry is short. If the session was created using an OAuth provider, this endpoint refreshes the access token from the provider.
/// <para><see href="https://appwrite.io/docs/references/1.5.x/client-rest/account#updateSession">Appwrite Docs</see></para>
/// </summary>
/// <param name="sessionId">Session ID. Use the string 'current' to update the current device session.</param>
/// <returns>The session</returns>
Task<AppwriteResult<Session>> UpdateSession(string sessionId = "current");
}
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 @@ -40,4 +40,7 @@ internal interface IAccountApi : IBaseApi

[Get("/account/sessions/{sessionId}")]
Task<IApiResponse<Session>> GetSession([Header("x-appwrite-session")] string? session, string sessionId);

[Patch("/account/sessions/{sessionId}")]
Task<IApiResponse<Session>> UpdateSession([Header("x-appwrite-session")] string? session, string sessionId);
}
23 changes: 21 additions & 2 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,28 @@ public async Task Run(string[] args)

Console.WriteLine("Getting Session...");

var account = await _client.Account.GetSession("6695d717983fadf6ece1");
//var response = await _client.Account.CreateEmailToken(new CreateEmailTokenRequest
//{
// Email = "[email protected]",
// UserId = "664aac1a00113f82e620"
//});

Console.WriteLine(account.Result.Match(
//var response = await _client.Account.CreateSession(new CreateSessionRequest
//{
// UserId = "664aac1a00113f82e620",
// Secret = "623341"
//});

var response = await _client.Account.GetSession("66a810f2e55b1329e25b");

var response2 = await _client.Account.UpdateSession("66a810f2e55b1329e25b");

Console.WriteLine(response.Result.Match(
account => account.ToString(),
appwriteError => appwriteError.Message,
internalERror => internalERror.Message));

Console.WriteLine(response2.Result.Match(
account => account.ToString(),
appwriteError => appwriteError.Message,
internalERror => internalERror.Message));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace PinguApps.Appwrite.Shared.Converters;

/// <summary>
/// This is only required temporarily as a workaround for #8447 on Appwrite.
/// <para><see href="https://github.com/appwrite/appwrite/issues/8447"/></para>
/// </summary>
public class MultiFormatDateTimeConverter : JsonConverter<DateTime>
{
private readonly string[] _formats = [
"yyyy-MM-ddTHH:mm:ss.fffK",
"yyyy-MM-dd HH:mm:ss.fff"
];

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var dateString = reader.GetString();

foreach (var format in _formats)
{
if (DateTime.TryParseExact(dateString, format, null, System.Globalization.DateTimeStyles.None, out var dateTime))
{
return dateTime;
}
}
throw new JsonException($"Unable to parse date: {dateString}");
}
throw new JsonException("Invalid token type");
}

public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_formats[0])); // Use the first format for serialization
}
}
2 changes: 1 addition & 1 deletion src/PinguApps.Appwrite.Shared/Responses/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public record Session(
[property: JsonPropertyName("$createdAt")] DateTime CreatedAt,
[property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt,
[property: JsonPropertyName("userId")] string UserId,
[property: JsonPropertyName("expire")] DateTime ExpiresAt,
[property: JsonPropertyName("expire"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime ExpiresAt,
[property: JsonPropertyName("provider")] string Provider,
[property: JsonPropertyName("providerUid")] string ProviderUserId,
[property: JsonPropertyName("providerAccessToken")] string ProviderAccessToken,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Net;
using PinguApps.Appwrite.Shared.Tests;
using PinguApps.Appwrite.Shared.Utils;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Client.Tests.Clients.Account;
public partial class AccountClientTests
{
[Fact]
public async Task UpdateSession_HitsCurrent_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
_mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/sessions/current")
.ExpectedHeaders(true)
.Respond(Constants.AppJson, Constants.UserResponse);

_appwriteClient.SetSession(Constants.Session);

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

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

[Fact]
public async Task UpdateSession_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
var sessionId = IdUtils.GenerateUniqueId();

_mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/sessions/{sessionId}")
.ExpectedHeaders(true)
.Respond(Constants.AppJson, Constants.UserResponse);

_appwriteClient.SetSession(Constants.Session);

// Act
var result = await _appwriteClient.Account.UpdateSession(sessionId);

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

[Fact]
public async Task UpdateSession_ShouldHandleException_WhenApiCallFails()
{
// Arrange
_mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/sessions/current")
.ExpectedHeaders(true)
.Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError);

_appwriteClient.SetSession(Constants.Session);

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

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

[Fact]
public async Task UpdateSession_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
_mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/sessions/current")
.ExpectedHeaders(true)
.Throw(new HttpRequestException("An error occurred"));

_appwriteClient.SetSession(Constants.Session);

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

// 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,88 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using PinguApps.Appwrite.Shared.Converters;

namespace PinguApps.Appwrite.Shared.Tests.Converters;
public class MultiFormatDateTimeConverterTests
{
private readonly JsonSerializerOptions _options;

public MultiFormatDateTimeConverterTests()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new MultiFormatDateTimeConverter());
}

[Fact]
public void Read_ValidDateStringWithTimeZone_ReturnsDateTime()
{
var json = "\"2023-01-01T00:00:00.000Z\"";
var result = JsonSerializer.Deserialize<DateTime>(json, _options);

// Convert both to UTC to compare
var expectedDateTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var actualDateTime = result.ToUniversalTime();

Assert.Equal(expectedDateTime, actualDateTime);
}

[Fact]
public void Read_ValidDateStringWithoutTimeZone_ReturnsDateTime()
{
var json = "\"2023-01-01 00:00:00.000\"";
var result = JsonSerializer.Deserialize<DateTime>(json, _options);
Assert.Equal(new DateTime(2023, 1, 1, 0, 0, 0), result);
}

[Fact]
public void Read_InvalidDateString_ThrowsJsonException()
{
var json = "\"invalid-date\"";
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DateTime>(json, _options));
}

[Fact]
public void Read_UnexpectedTokenType_ThrowsJsonException()
{
var json = "123";
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DateTime>(json, _options));
}

[Fact]
public void Write_ValidDateTime_WritesExpectedString()
{
var dateTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var json = JsonSerializer.Serialize(dateTime, _options);
Assert.Equal("\"2023-01-01T00:00:00.000Z\"", json);
}

public class MultiFormatDateTimeObject
{
[JsonPropertyName("x")]
[JsonConverter(typeof(MultiFormatDateTimeConverter))]
public DateTime X { get; set; }
}

[Fact]
public void Read_ValidDateStringInObject_ReturnsDateTime()
{
var json = "{\"x\": \"2023-01-01T00:00:00.000Z\"}";
var result = JsonSerializer.Deserialize<MultiFormatDateTimeObject>(json, _options);
Assert.NotNull(result);


// Convert both to UTC to compare
var expectedDateTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var actualDateTime = result.X.ToUniversalTime();

Assert.Equal(expectedDateTime, actualDateTime);
}

[Fact]
public void Write_ValidDateTimeInObject_WritesExpectedString()
{
var obj = new MultiFormatDateTimeObject { X = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
var json = JsonSerializer.Serialize(obj, _options);
Assert.Equal("{\"x\":\"2023-01-01T00:00:00.000Z\"}", json);
}
}