|
|
|
@@ -1,107 +1,173 @@
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
|
using Todo.Api.Hubs.Models;
|
|
|
|
|
using Todo.Core.Interfaces.Persistence;
|
|
|
|
|
|
|
|
|
|
namespace Todo.Api.Hubs
|
|
|
|
|
namespace Todo.Api.Hubs;
|
|
|
|
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
public class TodoHub : Hub
|
|
|
|
|
{
|
|
|
|
|
[Authorize]
|
|
|
|
|
public class TodoHub : Hub
|
|
|
|
|
private readonly ITodoRepository _todoRepository;
|
|
|
|
|
|
|
|
|
|
private static readonly ConcurrentDictionary<string, List<string>> ConnectedUsers = new();
|
|
|
|
|
|
|
|
|
|
public override Task OnConnectedAsync()
|
|
|
|
|
{
|
|
|
|
|
private readonly ITodoRepository _todoRepository;
|
|
|
|
|
var userId = Context.User.FindFirstValue("sub");
|
|
|
|
|
|
|
|
|
|
public TodoHub(ITodoRepository todoRepository)
|
|
|
|
|
// Try to get a List of existing user connections from the cache
|
|
|
|
|
ConnectedUsers.TryGetValue(userId, out var existingUserConnectionIds);
|
|
|
|
|
|
|
|
|
|
// happens on the very first connection from the user
|
|
|
|
|
existingUserConnectionIds ??= new List<string>();
|
|
|
|
|
|
|
|
|
|
// First add to a List of existing user connections (i.e. multiple web browser tabs)
|
|
|
|
|
existingUserConnectionIds.Add(Context.ConnectionId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add to the global dictionary of connected users
|
|
|
|
|
ConnectedUsers.TryAdd(userId, existingUserConnectionIds);
|
|
|
|
|
|
|
|
|
|
return base.OnConnectedAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Task OnDisconnectedAsync(Exception? exception)
|
|
|
|
|
{
|
|
|
|
|
var userId = Context.User.FindFirstValue("sub");
|
|
|
|
|
|
|
|
|
|
ConnectedUsers.TryGetValue(userId, out var existingUserConnectionIds);
|
|
|
|
|
|
|
|
|
|
// remove the connection id from the List
|
|
|
|
|
existingUserConnectionIds?.Remove(Context.ConnectionId);
|
|
|
|
|
|
|
|
|
|
// If there are no connection ids in the List, delete the user from the global cache (ConnectedUsers).
|
|
|
|
|
if (existingUserConnectionIds?.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_todoRepository = todoRepository;
|
|
|
|
|
// if there are no connections for the user,
|
|
|
|
|
// just delete the userName key from the ConnectedUsers concurent dictionary
|
|
|
|
|
ConnectedUsers.TryRemove(userId, out _);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task CreateTodo(string todoTitle, string projectName)
|
|
|
|
|
{
|
|
|
|
|
if (todoTitle is null)
|
|
|
|
|
throw new ArgumentException("title cannot be null");
|
|
|
|
|
var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName);
|
|
|
|
|
return base.OnDisconnectedAsync(exception);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos =
|
|
|
|
|
JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("getInboxTodos", serializedTodos);
|
|
|
|
|
}
|
|
|
|
|
public TodoHub(ITodoRepository todoRepository)
|
|
|
|
|
{
|
|
|
|
|
_todoRepository = todoRepository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UpdateTodo(string todoId, bool todoStatus)
|
|
|
|
|
{
|
|
|
|
|
await _todoRepository.UpdateTodoStatus(todoId, todoStatus);
|
|
|
|
|
public async Task CreateTodo(string todoTitle, string projectName)
|
|
|
|
|
{
|
|
|
|
|
if (todoTitle is null)
|
|
|
|
|
throw new ArgumentException("title cannot be null");
|
|
|
|
|
var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName);
|
|
|
|
|
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos =
|
|
|
|
|
JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse
|
|
|
|
|
{ Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("getInboxTodos", serializedTodos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task GetTodos()
|
|
|
|
|
{
|
|
|
|
|
var todos = await _todoRepository.GetTodosAsync();
|
|
|
|
|
var serializedTodos = JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos =
|
|
|
|
|
JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("todos", serializedTodos);
|
|
|
|
|
}
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("getInboxTodos", serializedTodos));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task GetInboxTodos()
|
|
|
|
|
{
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos = JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
|
|
|
|
|
public async Task UpdateTodo(string todoId, bool todoStatus)
|
|
|
|
|
{
|
|
|
|
|
await _todoRepository.UpdateTodoStatus(todoId, todoStatus);
|
|
|
|
|
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos =
|
|
|
|
|
JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse
|
|
|
|
|
{ Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("getInboxTodos", serializedTodos);
|
|
|
|
|
}
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("getInboxTodos", serializedTodos));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task GetTodo(string todoId)
|
|
|
|
|
public async Task GetTodos()
|
|
|
|
|
{
|
|
|
|
|
var todos = await _todoRepository.GetTodosAsync();
|
|
|
|
|
var serializedTodos = JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("todos", serializedTodos));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task GetInboxTodos()
|
|
|
|
|
{
|
|
|
|
|
var todos = await _todoRepository.GetNotDoneTodos();
|
|
|
|
|
var serializedTodos = JsonSerializer.Serialize(todos
|
|
|
|
|
.Select(t => new TodoResponse { Id = t.Id, Title = t.Title, Status = t.Status, Project = t.Project })
|
|
|
|
|
.ToList());
|
|
|
|
|
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("getInboxTodos", serializedTodos));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task GetTodo(string todoId)
|
|
|
|
|
{
|
|
|
|
|
var todo = await _todoRepository.GetTodoByIdAsync(todoId);
|
|
|
|
|
var serializedTodo = JsonSerializer.Serialize(new TodoResponse()
|
|
|
|
|
{
|
|
|
|
|
var todo = await _todoRepository.GetTodoByIdAsync(todoId);
|
|
|
|
|
var serializedTodo = JsonSerializer.Serialize(new TodoResponse()
|
|
|
|
|
{
|
|
|
|
|
Id = todo.Id,
|
|
|
|
|
Project = todo.Project,
|
|
|
|
|
Status = todo.Status,
|
|
|
|
|
Title = todo.Title,
|
|
|
|
|
});
|
|
|
|
|
Id = todo.Id,
|
|
|
|
|
Project = todo.Project,
|
|
|
|
|
Status = todo.Status,
|
|
|
|
|
Title = todo.Title,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("getTodo", serializedTodo);
|
|
|
|
|
}
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("getTodo", serializedTodo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task ReplaceTodo(string updateTodoRequest)
|
|
|
|
|
public async Task ReplaceTodo(string updateTodoRequest)
|
|
|
|
|
{
|
|
|
|
|
var updateTodo = JsonSerializer.Deserialize<UpdateTodoRequest>(updateTodoRequest);
|
|
|
|
|
if (updateTodo is null)
|
|
|
|
|
throw new InvalidOperationException("Could not parse invalid updateTodo");
|
|
|
|
|
|
|
|
|
|
var updatedTodo = await _todoRepository.UpdateTodoAsync(new Core.Entities.Todo()
|
|
|
|
|
{
|
|
|
|
|
var updateTodo = JsonSerializer.Deserialize<UpdateTodoRequest>(updateTodoRequest);
|
|
|
|
|
if (updateTodo is null)
|
|
|
|
|
throw new InvalidOperationException("Could not parse invalid updateTodo");
|
|
|
|
|
Id = updateTodo.Id,
|
|
|
|
|
Project = updateTodo.Project,
|
|
|
|
|
Status = updateTodo.Status,
|
|
|
|
|
Title = updateTodo.Title
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var updatedTodo = await _todoRepository.UpdateTodoAsync(new Core.Entities.Todo()
|
|
|
|
|
{
|
|
|
|
|
Id = updateTodo.Id,
|
|
|
|
|
Project = updateTodo.Project,
|
|
|
|
|
Status = updateTodo.Status,
|
|
|
|
|
Title = updateTodo.Title
|
|
|
|
|
});
|
|
|
|
|
var serializedTodo = JsonSerializer.Serialize(new TodoResponse()
|
|
|
|
|
{
|
|
|
|
|
Id = updatedTodo.Id,
|
|
|
|
|
Project = updatedTodo.Project,
|
|
|
|
|
Status = updatedTodo.Status,
|
|
|
|
|
Title = updatedTodo.Title,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var serializedTodo = JsonSerializer.Serialize(new TodoResponse()
|
|
|
|
|
{
|
|
|
|
|
Id = updatedTodo.Id,
|
|
|
|
|
Project = updatedTodo.Project,
|
|
|
|
|
Status = updatedTodo.Status,
|
|
|
|
|
Title = updatedTodo.Title,
|
|
|
|
|
});
|
|
|
|
|
await RunOnUserConnections(async (connections) =>
|
|
|
|
|
await Clients.Clients(connections).SendAsync("getTodo", serializedTodo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Clients.Caller.SendAsync("getTodo", serializedTodo);
|
|
|
|
|
}
|
|
|
|
|
private Task RunOnUserConnections(Func<IEnumerable<string>, Task> action)
|
|
|
|
|
{
|
|
|
|
|
var userId = Context.User.FindFirstValue("sub");
|
|
|
|
|
if (userId is null)
|
|
|
|
|
throw new InvalidOperationException("User id was invalid. Something has gone terribly wrong");
|
|
|
|
|
|
|
|
|
|
ConnectedUsers.TryGetValue(userId, out var connections);
|
|
|
|
|
|
|
|
|
|
if (connections is not null)
|
|
|
|
|
action(connections);
|
|
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
}
|