Refactor Some Endpoints To Work With Client V2

This commit is contained in:
Alan Moon 2025-12-14 13:22:22 -08:00
parent 4117727b68
commit 98f7c4cd96
12 changed files with 184 additions and 47 deletions

View File

@ -101,6 +101,15 @@ namespace qtc_api.Controllers
var token = await _tokenService.GenerateAccessTokenAndRefreshToken(dbUser.Data, true, request.RememberMe);
// check if the new user tag can be removed (14 days or 2 weeks)
var now = DateTime.UtcNow;
if(now.Date > dbUser.Data.CreatedAt.Date.AddDays(14) && dbUser.Data.Tags.Contains("new_user"))
{
List<string> _tagsList = [.. dbUser.Data.Tags];
_tagsList.Remove("new_user");
dbUser.Data.Tags = [.. _tagsList];
}
await dataContext.SaveChangesAsync();
return Ok(token);
}

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.TagHelpers;
using qtc_api.Services.BucketService;
namespace qtc_api.Controllers
{
[ApiController]
[Route("api/tags")]
public class TagController : ControllerBase
{
private readonly IBucketService _bucketService;
public TagController(IBucketService bucketService)
{
_bucketService = bucketService;
}
[HttpGet]
[Route("{tagName}")]
[Authorize]
public async Task<ActionResult> GetTagPicBytes(string tagName)
{
var _s3Res = await _bucketService.GetTagImageBytes(tagName);
if (_s3Res != null)
return new FileContentResult(_s3Res, "image/jpeg");
else
{
return NotFound(); // TODO - code the legacy way of finding images here (mostly for self-hosters)
}
}
}
}

View File

@ -73,10 +73,15 @@ namespace qtc_api.Controllers
if(id != null && id == user.Id)
{
var updatedUser = await _userService.UpdateUserInfo(user);
if (updatedUser.Success && updatedUser.Data != null)
{
await _chatGWContext.Clients.All.SendAsync("RefreshUserLists");
await _chatGWContext.Clients.All.SendAsync("RefreshContactsList");
await _chatGWContext.Clients.User(updatedUser.Data.Id).SendAsync("UpdateCurrentUser");
return Ok(updatedUser);
}
else return Ok();
} else
{
return Unauthorized("You are not authorized to edit that user.");
@ -116,6 +121,7 @@ namespace qtc_api.Controllers
stream.CopyTo(ms);
await _cache.SetImageAsync(recordId, ms.ToArray(), TimeSpan.FromHours(1));
await _chatGWContext.Clients.User(id).SendAsync("UpdateCurrentUser");
return Ok(response);
} else
{
@ -185,8 +191,12 @@ namespace qtc_api.Controllers
{
IEnumerable<Claim> claims = identity.Claims;
var id = claims.First().Value;
if (id != null) return Ok(await _userService.AddCurrencyToUser(id, amount, isSpinClaim));
var _res = await _userService.AddCurrencyToUser(id, amount, isSpinClaim);
if(_res.Success)
{
await _chatGWContext.Clients.User(id).SendAsync("UpdateCurrentUser");
return Ok(_res);
}
else return Ok(new ServiceResponse<int> { Success = false, Message = "Identity Has No User ID" });
}
else return Ok(new ServiceResponse<int> { Success = false, Message = "No Identity" });

View File

@ -13,5 +13,7 @@
public int Status { get; set; } = 0;
public int CurrencyAmount { get; set; } = 0;
public int ProfileCosmeticId { get; set; } = 0;
public string TextStatus { get; set; } = string.Empty;
public string[] Tags { get; set; } = [];
}
}

View File

@ -7,5 +7,6 @@
public string Bio { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
public int ProfileCosmeticId { get; set; } = 0;
public string TextStatus { get; set; } = string.Empty;
}
}

View File

@ -1,4 +1,5 @@
using qtc_api.Services.RoomService;
using Microsoft.AspNetCore.Identity;
using qtc_api.Services.RoomService;
using System.Text.Json;
namespace qtc_api.Hubs
@ -9,17 +10,25 @@ namespace qtc_api.Hubs
private IRoomService _roomService;
private ILogger<ChatHub> _logger;
private DataContext _dataContext;
private ServerConfig _serverConfig;
private static readonly Dictionary<string, List<User>> GroupUsers = [];
private static readonly Dictionary<string, List<string>> ConnectedUsers = [];
private static readonly List<User> OnlineUsers = [];
public static readonly Dictionary<string, List<User>> GroupUsers = [];
public static readonly Dictionary<string, List<string>> ConnectedUsers = [];
public static readonly List<User> OnlineUsers = [];
public ChatHub(IUserService userService, IRoomService roomService, ILogger<ChatHub> logger, DataContext dataContext)
private static User ServerUser = new()
{
Id = "0",
Username = "Server",
};
public ChatHub(IUserService userService, IRoomService roomService, ILogger<ChatHub> logger, DataContext dataContext, ServerConfig serverConfig)
{
_userService = userService;
_roomService = roomService;
_logger = logger;
_dataContext = dataContext;
_serverConfig = serverConfig;
}
public async override Task OnConnectedAsync()
@ -52,6 +61,7 @@ namespace qtc_api.Hubs
if (user.Data.Status == 0) await UpdateStatusAsync(user.Data, 1);
await Clients.Client(Context.ConnectionId).SendAsync("RefreshRoomList");
await Clients.Client(Context.ConnectionId).SendAsync("ReceiveServerConfig", _serverConfig);
}
}
}
@ -117,6 +127,7 @@ namespace qtc_api.Hubs
await Clients.All.SendAsync("RefreshUserLists");
await Clients.All.SendAsync("RefreshContactsList");
Log($"Status Was Set To {res.Data.Status} On User {user.Username}");
await Clients.Client(Context.ConnectionId).SendAsync("UpdateCurrentUser");
}
else
Log($"Something Went Wrong Setting The Status On User {user.Username}");
@ -128,12 +139,12 @@ namespace qtc_api.Hubs
{
await Groups.AddToGroupAsync(Context.ConnectionId, room.Id);
await Clients.Group(room.Id).SendAsync("RoomMessage", $"Server: User {user.Username} Has Joined {room.Name}");
await Clients.Group(room.Id).SendAsync("RoomMessage", ServerUser, $"User {user.Username} Has Joined {room.Name}", room);
if (!GroupUsers.TryGetValue(room.Id, out _)) { GroupUsers.Add(room.Id, new List<User>()); }
GroupUsers[room.Id].Add(user);
await Clients.Group(room.Id).SendAsync("RoomUserList", GroupUsers[room.Id]);
await Clients.Group(room.Id).SendAsync("RoomUserList", room, GroupUsers[room.Id]);
Log($"User {user.Username} Has Joined {room.Name}");
user.CurrentRoomId = room.Id;
@ -157,7 +168,7 @@ namespace qtc_api.Hubs
{
await Groups.AddToGroupAsync(Context.ConnectionId, room.Data.Id);
await Clients.Group(room.Data.Id).SendAsync("GuestJoin", username);
await Clients.Group(room.Data.Id).SendAsync("RoomMessage", ServerUser, $"Guest User {username} Has Joined {room.Data.Name}", room);
room.Data.UserCount += 1;
await _dataContext.SaveChangesAsync();
@ -172,11 +183,11 @@ namespace qtc_api.Hubs
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.Id);
await Clients.Group(room.Id).SendAsync("RoomMessage", $"Server: User {user.Username} Has Left {room.Name}");
await Clients.Group(room.Id).SendAsync("RoomMessage", ServerUser, $"User {user.Username} Has Left {room.Name}", room);
if (GroupUsers.TryGetValue(room.Id, out _)) GroupUsers[room.Id].Remove(GroupUsers[room.Id].FirstOrDefault(e => e.Id == user.Id)!);
await Clients.Group(room.Id).SendAsync("RoomUserList", GroupUsers[room.Id]);
await Clients.Group(room.Id).SendAsync("RoomUserList", room, GroupUsers[room.Id]);
Log($"User {user.Username} Has Left {room.Name}");
user.CurrentRoomId = string.Empty;
@ -194,7 +205,7 @@ namespace qtc_api.Hubs
[Authorize]
public async Task SendMessageAsync(User user, Message message, Room room)
{
await Clients.Group(room.Id).SendAsync("RoomMessage", $"{user.Username}: {message.Content}");
await Clients.Group(room.Id).SendAsync("RoomMessage", user, $"{message.Content}", room);
}
[HubMethodName("SendDirectMessage")]
@ -207,7 +218,7 @@ namespace qtc_api.Hubs
var connection = value.FirstOrDefault();
if(connection != null)
{
UserInformationDto userInformationDto = new UserInformationDto { Id = user.Id, Username = user.Username, Bio = user.Bio, Role = user.Role, Status = user.Status, CreatedAt = user.CreatedAt, DateOfBirth = user.DateOfBirth, ProfilePicture = user.ProfilePicture };
UserInformationDto userInformationDto = new UserInformationDto { Id = user.Id, Username = user.Username, Bio = user.Bio, Role = user.Role, Status = user.Status, CreatedAt = user.CreatedAt, DateOfBirth = user.DateOfBirth, ProfilePicture = user.ProfilePicture, ProfileCosmeticId = user.ActiveProfileCosmetic };
await Clients.Client(connection).SendAsync("ReceiveDirectMessage", message, userInformationDto);
return;
}

View File

@ -19,6 +19,8 @@
public int ActiveProfileCosmetic { get; set; } = 0;
public string CurrentRoomId { get; set; } = string.Empty;
public DateTime LastLogin { get; set; }
public string TextStatus { get; set; } = string.Empty;
public string[] Tags { get; set; } = [];
public virtual IEnumerable<RefreshToken>? RefreshTokens { get; }
public virtual IEnumerable<Contact>? ContactsMade { get; }

View File

@ -19,6 +19,7 @@ using qtc_api.Services.GameRoomService;
using qtc_api.Services.StoreService;
using qtc_api.Services.EmailService;
using qtc_api.Services.BucketService;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
@ -68,6 +69,28 @@ builder.Services.AddScoped<IRoomService, RoomService>();
builder.Services.AddScoped<IContactService, ContactService>();
builder.Services.AddScoped<StoreService>();
builder.Services.AddScoped<IBucketService, BucketService>();
builder.Services.AddScoped(provider => GetServerConfig());
ServerConfig GetServerConfig()
{
if (!File.Exists("./Resources/ServerConfig.json"))
throw new Exception("Server Config Doesn't Exist. Please Refer To The Repo For Examples.");
else
{
try
{
var _jsonText = File.ReadAllText("./Resources/ServerConfig.json");
ServerConfig? _config = JsonSerializer.Deserialize<ServerConfig>(_jsonText);
if (_config != null)
return _config;
else throw new Exception("Invalid Server Config. Please Refer To The Repo For Examples.");
}
catch (JsonException)
{
throw new Exception("Invalid Server Config. Please Refer To The Repo For Examples.");
}
}
}
builder.Services.AddSingleton<CurrencyGamesService>();
builder.Services.AddSingleton<GameRoomService>();

View File

@ -11,6 +11,7 @@ namespace qtc_api.Services.BucketService
public string AccessKey { get; private set; }
public string SecretKey { get; private set; }
public string ProfileImagesBucketName { get; private set; }
public string CDNBucketName { get; private set; }
public string ImagesBucketName { get; private set; }
private static AmazonS3Client S3Client;
@ -27,6 +28,7 @@ namespace qtc_api.Services.BucketService
var accessKey = _config["S3Config:S3AccessKey"];
var secretKey = _config["S3Config:S3SecretKey"];
var profileImagesBucket = _config["S3Config:S3ProfileImagesBucket"];
var cdnBucket = _config["S3Config:S3CDNBucket"];
var imagesBucket = _config["S3Config:S3ImagesBucket"];
var enabled = _config.GetValue<bool>("S3Config:S3Enabled");
@ -34,6 +36,7 @@ namespace qtc_api.Services.BucketService
if (accessKey != null) AccessKey = accessKey;
if (secretKey != null) SecretKey = secretKey;
if (profileImagesBucket != null) ProfileImagesBucketName = profileImagesBucket;
if (cdnBucket != null) CDNBucketName = cdnBucket;
if (imagesBucket != null) ImagesBucketName = imagesBucket;
Enabled = enabled;
@ -89,12 +92,37 @@ namespace qtc_api.Services.BucketService
if (response != null)
{
using (var stream = response.ResponseStream)
using (var ms = new MemoryStream())
{
using var stream = response.ResponseStream;
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}
else return default;
} catch (AmazonS3Exception ex)
{
_logger.LogError($"S3 Bucket Threw An Exception\n{ex.Message}\n{ex.StackTrace}");
return default;
}
}
public async Task<byte[]?> GetTagImageBytes(string tagName)
{
if (!Enabled)
{
_logger.LogWarning("Not Using S3 Bucket. Performance May Be Degraded.");
return default;
}
try
{
var response = await S3Client.GetObjectAsync(CDNBucketName, $@"tags/{tagName}.png");
if (response != null)
{
using var stream = response.ResponseStream;
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}
else return default;
} catch (AmazonS3Exception ex)

View File

@ -9,6 +9,7 @@
public string ImagesBucketName { get; }
public Task<bool> PutProfileImage(string userId, string imageName, byte[] imageBytes);
public Task<byte[]?> GetProfileImageBytes(string userId, string imageName);
public Task<byte[]?> GetTagImageBytes(string tagName);
public Task<bool> DeleteProfileImage(string userId, string imageName);
}
}

View File

@ -35,6 +35,9 @@ namespace qtc_api.Services.UserService
user.Role = _configuration["Jwt:DefaultUserRole"]!;
user.CreatedAt = DateTime.UtcNow;
// add new user tag
user.Tags = ["new_user"];
try
{
var cUser = userList.FirstOrDefault(x => x.Email == user.Email);
@ -174,19 +177,30 @@ namespace qtc_api.Services.UserService
if(user != null)
{
var dto = new UserInformationDto();
var dto = new UserInformationDto
{
Id = user.Id,
Username = user.Username,
ProfilePicture = user.ProfilePicture,
Role = user.Role,
Bio = user.Bio,
DateOfBirth = user.DateOfBirth,
CreatedAt = user.CreatedAt,
Status = user.Status,
CurrencyAmount = user.CurrencyAmount,
ProfileCosmeticId = user.ActiveProfileCosmetic,
LastLogin = user.LastLogin,
TextStatus = user.TextStatus,
Tags = user.Tags
};
dto.Id = user.Id;
dto.Username = user.Username;
dto.ProfilePicture = user.ProfilePicture;
dto.Role = user.Role;
dto.Bio = user.Bio;
dto.DateOfBirth = user.DateOfBirth;
dto.CreatedAt = user.CreatedAt;
dto.Status = user.Status;
dto.CurrencyAmount = user.CurrencyAmount;
dto.ProfileCosmeticId = user.ActiveProfileCosmetic;
dto.LastLogin = user.LastLogin;
// always add admin tag if user is admin
if(user.Role == "Admin")
{
List<string> _tagList = [.. dto.Tags];
_tagList.Add("admin");
dto.Tags = [.. _tagList];
}
serviceResponse.Success = true;
serviceResponse.Data = dto;
@ -219,21 +233,24 @@ namespace qtc_api.Services.UserService
dbUser.Bio = request.Bio;
dbUser.DateOfBirth = request.DateOfBirth;
dbUser.ActiveProfileCosmetic = request.ProfileCosmeticId;
dbUser.TextStatus = request.TextStatus;
await _dataContext.SaveChangesAsync();
var infoDto = new UserInformationDto();
infoDto.Id = dbUser.Id;
infoDto.Username = request.Username;
infoDto.ProfilePicture = dbUser.ProfilePicture;
infoDto.Bio = request.Bio;
infoDto.Role = dbUser.Role;
infoDto.DateOfBirth = request.DateOfBirth;
infoDto.CreatedAt = dbUser.CreatedAt;
infoDto.Status = dbUser.Status;
infoDto.CurrencyAmount = dbUser.CurrencyAmount;
infoDto.ProfileCosmeticId = request.ProfileCosmeticId;
var infoDto = new UserInformationDto
{
Id = dbUser.Id,
Username = request.Username,
ProfilePicture = dbUser.ProfilePicture,
Bio = request.Bio,
Role = dbUser.Role,
DateOfBirth = request.DateOfBirth,
CreatedAt = dbUser.CreatedAt,
Status = dbUser.Status,
CurrencyAmount = dbUser.CurrencyAmount,
ProfileCosmeticId = request.ProfileCosmeticId,
TextStatus = request.TextStatus
};
serviceResponse.Success = true;
serviceResponse.Data = infoDto;

View File

@ -20,6 +20,7 @@
"S3AccessKey": "",
"S3SecretKey": "",
"S3ProfileImagesBucket": "qtcnet-profileimages",
"S3CDNBucket": "qtcnet-cdn",
"S3ImagesBucket": "qtcnet-images"
},
"Logging": {