Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
14e3222
Merge pull request #1 from taiphanvan2k3/feat/setup-project-with-clea…
taiphanvan2k3 Sep 18, 2025
5cd882a
Merge pull request #2 from taiphanvan2k3/chore/write-documents-and-re…
taiphanvan2k3 Sep 20, 2025
bae25df
Bump FluentValidation from 11.9.2 to 12.0.0
dependabot[bot] Sep 20, 2025
1d5ed73
Bump FluentValidation.AspNetCore from 11.3.0 to 11.3.1
dependabot[bot] Sep 20, 2025
1485c37
Merge pull request #5 from taiphanvan2k3/dependabot/nuget/src/Applica…
taiphanvan2k3 Sep 20, 2025
55cff40
Merge pull request #6 from taiphanvan2k3/dependabot/nuget/src/Web.Api…
taiphanvan2k3 Sep 20, 2025
26c3d93
feat: Configure Docker and test with simple endpoints (#8)
taiphanvan2k3 Sep 20, 2025
bdf06e1
chore: Update readme (#9)
taiphanvan2k3 Sep 20, 2025
1d1edda
Bump AspNetCore.HealthChecks.Redis from 8.0.1 to 9.0.0 (#3)
dependabot[bot] Sep 20, 2025
885ad8f
Bump AspNetCore.HealthChecks.SqlServer from 8.0.2 to 9.0.0 (#4)
dependabot[bot] Sep 20, 2025
f7c3942
chore: Init Schema (#18)
taiphanvan2k3 Oct 27, 2025
5dbaac4
Bump Microsoft.AspNetCore.Authentication.JwtBearer from 8.0.0 to 8.0.…
dependabot[bot] Oct 27, 2025
cd09f4e
Bump Microsoft.AspNetCore.OpenApi from 8.0.10 to 8.0.21 (#17)
dependabot[bot] Oct 27, 2025
957c556
Bump Microsoft.EntityFrameworkCore and Npgsql.EntityFrameworkCore.Pos…
dependabot[bot] Oct 27, 2025
fac23ef
Bump Microsoft.EntityFrameworkCore.Design and Microsoft.EntityFramewo…
dependabot[bot] Oct 27, 2025
a76fe4c
Bump Microsoft.AspNetCore.Authentication.JwtBearer from 8.0.20 to 8.0…
dependabot[bot] Oct 27, 2025
d125d83
feat: Implement Identity (#24)
taiphanvan2k3 Oct 27, 2025
25e5b68
feat: Setup email configure and complete register endpoint (#32)
taiphanvan2k3 Nov 3, 2025
27c4e83
chore: Update dependabot content (#33)
taiphanvan2k3 Nov 3, 2025
948e24f
Bump Microsoft.AspNetCore.Identity.EntityFrameworkCore from 8.0.11 to…
dependabot[bot] Nov 3, 2025
dc6ab4c
feat: Implement verify email (#35)
taiphanvan2k3 Nov 3, 2025
b863848
Bump Microsoft.EntityFrameworkCore.Design and Swashbuckle.AspNetCore …
dependabot[bot] Nov 11, 2025
1ccec05
Dependabot/nuget/src/application/minor and patch 5e1fe4b923 (#39)
taiphanvan2k3 Nov 11, 2025
4d49a7c
Bump Microsoft.Extensions.Caching.StackExchangeRedis and 3 others (#37)
dependabot[bot] Nov 11, 2025
c8350d8
feat: Implement Google Login API (#41)
taiphanvan2k3 Nov 14, 2025
9de8b85
feat: Add CI/CD scripts for Google Cloud deployment (#42)
taiphanvan2k3 Nov 14, 2025
81b95bd
feat: Update Google Cloud deployment configuration and add production…
taiphanvan2k3 Nov 14, 2025
a36378d
feat: Update deployment configuration and production settings for Clo…
taiphanvan2k3 Nov 14, 2025
eabc176
feat: Allow production environment to access Swagger UI for testing (…
taiphanvan2k3 Nov 14, 2025
115612c
feat: Implement transaction handling and add AvatarUrl to User entity…
taiphanvan2k3 Nov 14, 2025
78ef231
feat: Implement send message API (#47)
taiphanvan2k3 Nov 15, 2025
e537c9c
feat: Implement api for CRUD conversation (#48)
taiphanvan2k3 Nov 18, 2025
e962c33
refactor: Update ConversationController to use HttpPatch for title up…
taiphanvan2k3 Nov 21, 2025
a19533c
fix: Recreate the wrong migration when creating full text search
taiphanvan2k3 Nov 21, 2025
677ef8b
Merge pull request #50 from DUT-Team-21TCLC-DT3/fix/recreate-add-full…
taiphanvan2k3 Nov 21, 2025
85fcfed
feat: Implement refresh token functionality and enhance authenticatio…
taiphanvan2k3 Nov 21, 2025
2190a9e
feat: Enhance security in token management with SecurityException and…
taiphanvan2k3 Nov 21, 2025
f3ca946
Merge pull request #51 from DUT-Team-21TCLC-DT3/feat/handle-remember-…
taiphanvan2k3 Nov 21, 2025
1dd3a30
refactor: Remove unused behaviors and exceptions, streamline error ha…
taiphanvan2k3 Nov 22, 2025
b404005
feat: Implement custom authorization filter and enhance JWT authentic…
taiphanvan2k3 Nov 22, 2025
b20dd7f
Merge pull request #52 from DUT-Team-21TCLC-DT3/refactor/change-all-c…
taiphanvan2k3 Nov 22, 2025
09a6870
feat: Implement external login functionality and user profile management
taiphanvan2k3 Nov 22, 2025
f2897b5
Merge pull request #53 from DUT-Team-21TCLC-DT3/feat/implement-profil…
taiphanvan2k3 Nov 22, 2025
d8d67ef
feat: Implement message deletion and update functionality
taiphanvan2k3 Nov 23, 2025
8f45b7d
Merge pull request #54 from DUT-Team-21TCLC-DT3/feat/implement-rename…
taiphanvan2k3 Nov 23, 2025
285a855
feat: Implement AI content generation with SignalR integration
taiphanvan2k3 Nov 23, 2025
fa00d61
Merge pull request #55 from DUT-Team-21TCLC-DT3/feat/integrate-with-s…
taiphanvan2k3 Nov 23, 2025
6549e39
refactor: Simplify GetHistoriesRequest and enhance error handling
taiphanvan2k3 Nov 23, 2025
9f700b8
Merge pull request #56 from DUT-Team-21TCLC-DT3/fix/wrong-cache-key-a…
taiphanvan2k3 Nov 23, 2025
8c03b05
feat: Add SwaggerController for Postman collection generation
taiphanvan2k3 Nov 23, 2025
3de63b8
Merge pull request #57 from DUT-Team-21TCLC-DT3/feat/implement-add-en…
taiphanvan2k3 Nov 23, 2025
367e386
refactor: Enhance SwaggerController for improved Postman collection g…
taiphanvan2k3 Nov 24, 2025
9e54c78
Merge pull request #58 from DUT-Team-21TCLC-DT3/feat/implement-add-en…
taiphanvan2k3 Nov 24, 2025
366833a
feat: Implement AI thinking activity and thought tracking
taiphanvan2k3 Nov 24, 2025
252b725
Merge pull request #59 from DUT-Team-21TCLC-DT3/feat/implement-saving…
taiphanvan2k3 Nov 24, 2025
f60be36
feat: Implement WebSocket support for AI content generation
taiphanvan2k3 Nov 27, 2025
ddca6bf
fix: Update error handling in WebSocketChatController and RawWebSocke…
taiphanvan2k3 Nov 27, 2025
2e84750
refactor: Improve WebSocketChatController error handling and resource…
taiphanvan2k3 Nov 27, 2025
9a3cc6c
Merge pull request #60 from DUT-Team-21TCLC-DT3/feat/implement-websoc…
taiphanvan2k3 Nov 27, 2025
3721316
feat: Integrate with AI service and configuration updates
taiphanvan2k3 Nov 29, 2025
1711b63
Merge pull request #61 from DUT-Team-21TCLC-DT3/feat/integrate-with-a…
taiphanvan2k3 Nov 29, 2025
7aed923
refactor: Remove unused namespaces
taiphanvan2k3 Nov 30, 2025
eaf9c4b
feat: Implement PDF export functionality for conversations
taiphanvan2k3 Nov 30, 2025
3c0c872
Merge pull request #62 from DUT-Team-21TCLC-DT3/feat/implement-export…
taiphanvan2k3 Nov 30, 2025
1b77768
fix: Enhance PDF service configuration for Linux environment
taiphanvan2k3 Nov 30, 2025
a9d1166
Merge pull request #63 from DUT-Team-21TCLC-DT3/fix/cannot-export-pdf…
taiphanvan2k3 Nov 30, 2025
034dff5
fix: Change verififcation url from localhost to remote
taiphanvan2k3 Dec 1, 2025
29c658e
feat: Configure to upload file to Google Storage Bucket
taiphanvan2k3 Dec 1, 2025
e1a1ece
Merge pull request #64 from DUT-Team-21TCLC-DT3/fix/change-verify-ema…
taiphanvan2k3 Dec 1, 2025
c6044a7
fix: Enable IronPdf support for Linux by adding the necessary package…
taiphanvan2k3 Dec 1, 2025
33b9b61
Merge pull request #65 from DUT-Team-21TCLC-DT3/fix/missing-iron-pdf-…
taiphanvan2k3 Dec 1, 2025
700c6dd
feat: Implement OpenTelemetry tracing and update Google Cloud credent…
taiphanvan2k3 Dec 1, 2025
f58e90a
Merge pull request #66 from DUT-Team-21TCLC-DT3/feat/setup-open-telem…
taiphanvan2k3 Dec 1, 2025
06cbcdc
fix: Update OpenTelemetry configuration for Google Cloud tracing
taiphanvan2k3 Dec 1, 2025
31745c0
Merge pull request #67 from DUT-Team-21TCLC-DT3/feat/setup-open-telem…
taiphanvan2k3 Dec 1, 2025
6e392f0
feat: Enhance OpenTelemetry tracing configuration for Google Cloud
taiphanvan2k3 Dec 2, 2025
f26074a
Merge pull request #68 from DUT-Team-21TCLC-DT3/feat/setup-open-telem…
taiphanvan2k3 Dec 2, 2025
7f39670
feat: Enhance Entity Framework Core logging and OpenTelemetry configu…
taiphanvan2k3 Dec 2, 2025
2cb5a6c
Merge pull request #69 from DUT-Team-21TCLC-DT3/fix/missing-ef-trace-…
taiphanvan2k3 Dec 2, 2025
8f589d4
fix: Missing trace from EF
taiphanvan2k3 Dec 2, 2025
9675211
Merge pull request #70 from DUT-Team-21TCLC-DT3/fix/missing-ef-trace-…
taiphanvan2k3 Dec 2, 2025
7239e12
feat: Configure to use Aspire Dashboard
taiphanvan2k3 Dec 2, 2025
71bf097
Merge pull request #71 from DUT-Team-21TCLC-DT3/feat/configure-to-use…
taiphanvan2k3 Dec 2, 2025
0a4b906
feat: Implement conversation statistics query and conversations list
taiphanvan2k3 Dec 2, 2025
4d2e2a0
Merge pull request #72 from DUT-Team-21TCLC-DT3/feat/enhance-response…
taiphanvan2k3 Dec 2, 2025
3aca7ba
refactor: Cannot verify email
taiphanvan2k3 Dec 2, 2025
e1b9192
Merge pull request #73 from DUT-Team-21TCLC-DT3/fix/cannot-verify-email
taiphanvan2k3 Dec 2, 2025
6203537
fix: Error sql when searching list of histories
taiphanvan2k3 Dec 2, 2025
ad73391
Merge pull request #74 from DUT-Team-21TCLC-DT3/fix/error-sql-when-se…
taiphanvan2k3 Dec 2, 2025
fab8def
feat(deploy): Optimize docker build time
taiphanvan2k3 Dec 2, 2025
5563199
Merge pull request #75 from DUT-Team-21TCLC-DT3/feat/optimize-build-time
taiphanvan2k3 Dec 2, 2025
8fb88b3
feat: Add comprehensive unit tests for application features
taiphanvan2k3 Dec 3, 2025
57b882f
feat: Add unit tests for authentication and user profile features
taiphanvan2k3 Dec 3, 2025
e451a90
Merge pull request #76 from DUT-Team-21TCLC-DT3/feat/apply-unit-test
taiphanvan2k3 Dec 3, 2025
e6dff14
Add retention days for uploaded artifacts
taiphanvan2k3 Dec 3, 2025
dfcbc8a
Set retention days for published app artifacts
taiphanvan2k3 Dec 3, 2025
b150e23
Comment out build artifacts upload step
taiphanvan2k3 Dec 3, 2025
c61b7c9
chore: Update CORS policy to include new origin
taiphanvan2k3 Dec 4, 2025
0e34c0e
Merge pull request #77 from DUT-Team-21TCLC-DT3/chore/update-cors-for…
taiphanvan2k3 Dec 4, 2025
80a6d17
feat: Implement message reporting feature
taiphanvan2k3 Dec 5, 2025
3ccfa9e
Merge pull request #78 from DUT-Team-21TCLC-DT3/feat/implement-messag…
taiphanvan2k3 Dec 5, 2025
4a938e4
Merge branch 'main' into main
taiphanvan2k3 Dec 17, 2025
816baa6
Merge pull request #81 from taiphanvan2k3/main
taiphanvan2k3 Dec 17, 2025
f2ceb1a
Bump the minor-and-patch group with 24 updates
dependabot[bot] Dec 22, 2025
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
Prev Previous commit
Next Next commit
feat: Implement WebSocket support for AI content generation
- Added WebSocketChatController to handle WebSocket connections for AI chat streaming, replacing SignalR for mobile clients.
- Introduced RawWebSocketNotifier for sending JSON messages over WebSocket.
- Enhanced GenerateAIContentQuery and its handler to utilize a custom notifier for streaming AI responses.
- Updated authentication logic to support WebSocket connections.
- Added a test HTML page for testing the WebSocket functionality.
- Removed obsolete Web.Api.http file.
  • Loading branch information
taiphanvan2k3 committed Nov 27, 2025
commit f60be36b879b3aa9d23f2caa8e6589202daae277
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Application.Common;
using Application.Interfaces.Services;
using Domain.Common;
using MediatR;

Expand All @@ -13,4 +14,10 @@ public sealed class GenerateAIContentQuery : IRequest<Result<Unit>>
public Guid MessageId { get; set; }
public Guid ConversationId { get; set; }
public string ConnectionId { get; set; }

/// <summary>
/// Custom notifier để sử dụng thay thế cho SignalR (ví dụ: WebSocket raw)
/// Nếu null thì sử dụng IChatNotifier được inject từ DI
/// </summary>
public IChatNotifier? CustomNotifier { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public sealed class GenerateAIContentQueryHandler(

public async Task<Result<Unit>> Handle(GenerateAIContentQuery request, CancellationToken cancellationToken)
{
// Chọn notifier: ưu tiên CustomNotifier (WebSocket), fallback về SignalR
var notifier = request.CustomNotifier ?? _chatNotifier;

// 1. Validate message exists and belongs to conversation
var message = await _messageRepository.GetByIdAsync(request.MessageId, cancellationToken);
if (message == null)
Expand Down Expand Up @@ -102,15 +105,15 @@ await _aiService.GenerateResponseStreamAsync(
{
fullResponse += chunk;

await _chatNotifier.SendChunkAsync(request.ConnectionId,
await notifier.SendChunkAsync(request.ConnectionId,
request.MessageId, request.ConversationId, chunk, cancellationToken);
},
onThought: async (thought) =>
{
thoughtStep++;

// 1. Gửi notification cho client ngay lập tức để UI phản hồi nhanh
await _chatNotifier.SendThoughtAsync(request.ConnectionId,
await notifier.SendThoughtAsync(request.ConnectionId,
request.MessageId, request.ConversationId, thought, cancellationToken);

var thoughtEntity = new Thought
Expand All @@ -133,7 +136,7 @@ await _chatNotifier.SendThoughtAsync(request.ConnectionId,
await _unitOfWork.SaveChangesAsync(cancellationToken);

// 7. Notify client that streaming is complete
await _chatNotifier.SendCompleteAsync(request.ConnectionId, request.MessageId,
await notifier.SendCompleteAsync(request.ConnectionId, request.MessageId,
request.ConversationId, fullResponse, cancellationToken);

return Result.Success(Unit.Value);
Expand All @@ -148,7 +151,7 @@ await _chatNotifier.SendCompleteAsync(request.ConnectionId, request.MessageId,
}

// Notify client about error
await _chatNotifier.SendErrorAsync(request.ConnectionId, request.MessageId,
await notifier.SendErrorAsync(request.ConnectionId, request.MessageId,
"Failed to generate AI response: " + (ex.InnerException?.Message ?? ex.Message), cancellationToken);

return Result.Failure<Unit>(Error.Failure("AI.GenerationFailed",
Expand Down
91 changes: 91 additions & 0 deletions src/Web.Api/Controllers/WebSocketChatController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Application.Features.Conversation.GenerateAIContent;
using Application.Interfaces.Services;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net.WebSockets;
using Web.Api.Services;

namespace Web.Api.Controllers;

/// <summary>
/// Controller xử lý WebSocket connections cho AI chat streaming
/// Thay thế cho SignalR khi mobile client không thể tích hợp SignalR
/// </summary>
[Route("ws/chat")]
[Authorize]
public class WebSocketChatController(IMediator mediator, ILogger<WebSocketChatController> logger) : ControllerBase
{
private readonly IMediator _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
private readonly ILogger<WebSocketChatController> _logger = logger ?? throw new ArgumentNullException(nameof(logger));

/// <summary>
/// Kết nối WebSocket để streaming AI response
/// </summary>
/// <param name="conversationId">ID của conversation</param>
/// <param name="messageId">ID của message cần generate AI response</param>
[HttpGet("connect")]
public async Task Connect([FromQuery] Guid conversationId, [FromQuery] Guid messageId, CancellationToken cancellationToken)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}

// 1. Chấp nhận kết nối WebSocket
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var connectionId = Guid.NewGuid().ToString();

_logger.LogInformation("WebSocket Connected: {ConnId} for Message {MessageId}", connectionId, messageId);

try
{
// 2. Tạo WebSocket notifier wrapper
var wsNotifier = new RawWebSocketNotifier(webSocket);

// 3. Tạo Query và gán CustomNotifier
var query = new GenerateAIContentQuery
{
ConversationId = conversationId,
MessageId = messageId,
ConnectionId = connectionId,
CustomNotifier = wsNotifier // Sử dụng WebSocket thay vì SignalR
};

// 4. Gọi Handler (Handler sẽ dùng wsNotifier để streaming)
var result = await _mediator.Send(query, cancellationToken);

if (result.IsFailure)
{
// Gửi lỗi về WebSocket nếu Handler fail logic
await wsNotifier.SendErrorAsync(connectionId, messageId, result.Error.Description, cancellationToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "WebSocket Error for Connection {ConnId}", connectionId);

// Gửi lỗi về WebSocket nếu có exception
try
{
using var wsNotifier = new RawWebSocketNotifier(webSocket);
await wsNotifier.SendErrorAsync(connectionId, messageId, ex.Message, cancellationToken);
}
catch
{
// Ignore errors when sending error message
}
}
finally
{
// 5. Đóng WebSocket khi xong việc
if (webSocket.State == WebSocketState.Open)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None);
}

_logger.LogInformation("🔌 WebSocket Disconnected: {ConnId}", connectionId);
}
}
}
2 changes: 1 addition & 1 deletion src/Web.Api/Extensions/AuthenticationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static IServiceCollection AddJwtAuthentication(

// Nếu request có token ở query VÀ đường dẫn bắt đầu bằng /chatHub
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chatHub"))
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chatHub") || path.StartsWithSegments("/ws/chat")))
{
// Lấy token từ query nhét vào Context để cho thằng .NET nó validate
context.Token = accessToken;
Expand Down
3 changes: 3 additions & 0 deletions src/Web.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
// app.UseCors("ProductionPolicy");
app.UseCors("DevelopmentPolicy");

// Add WebSocket support
app.UseWebSockets();

// Add Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();
Expand Down
98 changes: 98 additions & 0 deletions src/Web.Api/Services/RawWebSocketNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using Application.Interfaces.Services;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

namespace Web.Api.Services;

/// <summary>
/// WebSocket implementation của IChatNotifier
/// Gửi JSON messages qua WebSocket raw thay vì SignalR
/// </summary>
public class RawWebSocketNotifier(WebSocket socket) : IChatNotifier, IDisposable
{
private readonly WebSocket _socket = socket ?? throw new ArgumentNullException(nameof(socket));

// Dùng Semaphore để đảm bảo không bắn 2 tin cùng lúc gây crash socket
private readonly SemaphoreSlim _lock = new(1, 1);

/// <summary>
/// Gửi JSON message qua WebSocket
/// </summary>
private async Task SendJsonAsync(object data, CancellationToken cancellationToken)
{
var json = JsonSerializer.Serialize(data);
var bytes = Encoding.UTF8.GetBytes(json);

await _lock.WaitAsync(cancellationToken);
try
{
if (_socket.State == WebSocketState.Open)
{
await _socket.SendAsync(
new ArraySegment<byte>(bytes),
WebSocketMessageType.Text,
true, // End of message
cancellationToken);
}
}
finally
{
_lock.Release();
}
}

/// <summary>
/// Gửi chunk của AI response
/// </summary>
public async Task SendChunkAsync(string connectionId, Guid messageId, Guid conversationId, string chunk, CancellationToken cancellationToken)
{
// Format JSON giống SignalR event để Client dễ parse
await SendJsonAsync(new
{
target = "ReceiveChunk",
arguments = new[] { new { MessageId = messageId, Chunk = chunk } }
}, cancellationToken);
}

/// <summary>
/// Gửi thought step của AI reasoning
/// </summary>
public async Task SendThoughtAsync(string connectionId, Guid messageId, Guid conversationId, string stepDescription, CancellationToken cancellationToken)
{
await SendJsonAsync(new
{
target = "ReceiveThought",
arguments = new[] { new { MessageId = messageId, Step = stepDescription } }
}, cancellationToken);
}

/// <summary>
/// Thông báo streaming hoàn thành
/// </summary>
public async Task SendCompleteAsync(string connectionId, Guid messageId, Guid conversationId, string fullContent, CancellationToken cancellationToken)
{
await SendJsonAsync(new
{
target = "StreamingComplete",
arguments = new[] { new { MessageId = messageId } }
}, cancellationToken);
}

/// <summary>
/// Gửi lỗi về client
/// </summary>
public async Task SendErrorAsync(string connectionId, Guid messageId, string errorMessage, CancellationToken cancellationToken)
{
await SendJsonAsync(new
{
target = "StreamingError",
arguments = new[] { new { MessageId = messageId, Error = errorMessage } }
}, cancellationToken);
}

public void Dispose()
{
_socket?.Dispose();
}
}
6 changes: 0 additions & 6 deletions src/Web.Api/Web.Api.http

This file was deleted.

Loading