Add remaining project files
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
using DeluxeBackend.Models;
|
||||
using DeluxeBackend.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Drawing;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using static DeluxeBackend.Enums;
|
||||
|
||||
namespace DeluxeBackend.Controllers.Api
|
||||
{
|
||||
[Route("api/customAvatarItems")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class CustomAvatarItemsController(
|
||||
ILiteDbService db,
|
||||
IJwtService jwt,
|
||||
DiscordBotService discord,
|
||||
ICdnService cdn,
|
||||
HttpClient httpClient) : ControllerBase
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
|
||||
[HttpGet("v1/isCreationEnabled")]
|
||||
[HttpGet("v1/isRenderingEnabled")]
|
||||
public IActionResult IsCreationEnabled() => Ok(true);
|
||||
|
||||
[HttpGet("v1/minPriceForPublicItem")]
|
||||
public IActionResult MinPriceForPublicItem() => Ok(0);
|
||||
|
||||
[HttpGet("v1/isCreationAllowedForAccount")]
|
||||
public async Task<IActionResult> IsCreationAllowedForAccount()
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
if (!account.DiscordId.HasValue)
|
||||
return Ok(new { Success = false });
|
||||
|
||||
bool hasBoosted = await discord.HasUserBoostedGuild(account.DiscordId.Value);
|
||||
return Ok(new { Success = hasBoosted });
|
||||
}
|
||||
|
||||
[HttpGet("/econ/customAvatarItems/v1/owned")]
|
||||
public async Task<IActionResult> Owned([FromQuery] int skip = 0, [FromQuery] int take = 100)
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
const bool isCreator = true;
|
||||
Expression<Func<CustomAvatarItem, bool>> filter = x =>
|
||||
x.Creator.Id == account.Id && (isCreator || x.Accessibility == RoomAccessibility.Public);
|
||||
|
||||
return GetPagedItems(filter, skip, take);
|
||||
}
|
||||
|
||||
[HttpGet("v2/fromCreator/{accId}")]
|
||||
public async Task<IActionResult> FromCreator(long accId, [FromQuery] int skip = 0, [FromQuery] int take = 100)
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
bool isCreator = account.Id == accId;
|
||||
Expression<Func<CustomAvatarItem, bool>> filter = x =>
|
||||
x.Creator.Id == accId && (isCreator || x.Accessibility == RoomAccessibility.Public);
|
||||
|
||||
return GetPagedItems(filter, skip, take);
|
||||
}
|
||||
|
||||
[HttpPost("v1")]
|
||||
public async Task<IActionResult> Create(
|
||||
[FromForm(Name = "thumbnailImage"), Required] IFormFile thumbnailImage,
|
||||
[FromForm(Name = "design"), Required] IFormFile designImage,
|
||||
[FromForm(Name = "metadata"), Required] string metaJson)
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
if (!account.DiscordId.HasValue || !await discord.HasUserBoostedGuild(account.DiscordId.Value))
|
||||
return StatusCode(StatusCodes.Status403Forbidden);
|
||||
|
||||
CustomAvatarItemMetaDTO? meta;
|
||||
try
|
||||
{
|
||||
meta = JsonSerializer.Deserialize<CustomAvatarItemMetaDTO>(metaJson);
|
||||
if (meta == null) return BadRequest("Invalid metadata format.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest("Metadata is not valid JSON.");
|
||||
}
|
||||
|
||||
using var thumbStream = thumbnailImage.OpenReadStream();
|
||||
string? thumbRemotePath = await cdn.UploadFile(thumbStream, "img");
|
||||
if (thumbRemotePath == null)
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to upload ThumbnailImage to the CDN.");
|
||||
|
||||
using var designStream = designImage.OpenReadStream();
|
||||
string? designRemotePath = await cdn.UploadFile(designStream, "img");
|
||||
if (designRemotePath == null)
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to upload DesignImage to the CDN.");
|
||||
|
||||
var customAvatar = new CustomAvatarItem
|
||||
{
|
||||
Creator = account,
|
||||
Name = meta.Name,
|
||||
Price = meta.Price,
|
||||
BaseAvatarItemColor = ColorTranslator.FromHtml(meta.BaseAvatarItemColor),
|
||||
ThumbnailImageFilename = thumbRemotePath,
|
||||
DesignFilename = designRemotePath,
|
||||
PreviewOrientation = meta.PreviewOrientation
|
||||
};
|
||||
|
||||
db.CustomAvatarItems.Insert(customAvatar);
|
||||
return Ok(new { Success = true, Value = customAvatar.ToDictionary() });
|
||||
}
|
||||
|
||||
[HttpPost("v1/bulk")]
|
||||
public async Task<IActionResult> Bulk([FromForm(Name = "customAvatarItemIds")] List<Guid> customAvatarItemIds)
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
var items = db.CustomAvatarItems.Find(x => customAvatarItemIds.Contains(x.Id)).ToList();
|
||||
var foundIds = items.Select(x => x.Id).ToHashSet();
|
||||
var missingIds = customAvatarItemIds.Where(id => !foundIds.Contains(id)).ToList();
|
||||
|
||||
if (missingIds.Any())
|
||||
{
|
||||
var importAccount = GetOrCreateImportAccount();
|
||||
|
||||
foreach (var missingId in missingIds)
|
||||
{
|
||||
var fallbackItem = await FetchItemFromRecNetAsync(missingId, importAccount);
|
||||
if (fallbackItem != null)
|
||||
{
|
||||
items.Add(fallbackItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(items.Select(x => x.ToDictionary()).ToList());
|
||||
}
|
||||
|
||||
[HttpGet("v1/{customAvatarItemId}")]
|
||||
public async Task<IActionResult> Get(Guid customAvatarItemId)
|
||||
{
|
||||
var account = await GetAuthenticatedAccountAsync();
|
||||
if (account == null) return Unauthorized();
|
||||
|
||||
var customAvatarItem = db.CustomAvatarItems.FindById(customAvatarItemId);
|
||||
|
||||
if (customAvatarItem == null)
|
||||
{
|
||||
var importAccount = GetOrCreateImportAccount();
|
||||
customAvatarItem = await FetchItemFromRecNetAsync(customAvatarItemId, importAccount);
|
||||
}
|
||||
|
||||
if (customAvatarItem == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(customAvatarItem.ToDictionary());
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private async Task<Account?> GetAuthenticatedAccountAsync()
|
||||
{
|
||||
var login = await jwt.GetLoginWithInfo(User);
|
||||
return login?.Account;
|
||||
}
|
||||
|
||||
private IActionResult GetPagedItems(Expression<Func<CustomAvatarItem, bool>> filter, int skip, int take)
|
||||
{
|
||||
int totalCount = db.CustomAvatarItems.Count(filter);
|
||||
var results = db.CustomAvatarItems
|
||||
.Include(x => x.Creator)
|
||||
.Find(filter)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.Select(x => x.ToDictionary());
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Results = results,
|
||||
TotalResults = totalCount
|
||||
});
|
||||
}
|
||||
|
||||
private Account GetOrCreateImportAccount()
|
||||
{
|
||||
var importAccount = db.Accounts.FindOne(x => x.Username == "Import");
|
||||
if (importAccount == null)
|
||||
{
|
||||
importAccount = new Account
|
||||
{
|
||||
Username = "Import",
|
||||
DisplayName = "Import",
|
||||
Birthday = DateOnly.MinValue,
|
||||
ImageName = "DefaultProfileImage",
|
||||
IsRecentHistoryVisible = false,
|
||||
};
|
||||
importAccount.Roles.AddRange(["developer", "keepsake", "livekeepsakeeventroomsaveoverride", "betaroomcurrencycreator", "multiinstanceevent"]);
|
||||
db.Accounts.Insert(importAccount);
|
||||
}
|
||||
return importAccount;
|
||||
}
|
||||
|
||||
private async Task<CustomAvatarItem?> FetchItemFromRecNetAsync(Guid itemId, Account importAccount)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.rec.net/api/customAvatarItems/v1/{itemId}");
|
||||
request.Headers.Add("Authorization", $"Bearer {ServerConfig.RRToken}");
|
||||
|
||||
using var response = await httpClient.SendAsync(request);
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var recNetResult = JsonSerializer.Deserialize<CAvatarItemDto>(content, _jsonOptions);
|
||||
|
||||
if (recNetResult != null && !string.IsNullOrEmpty(recNetResult.DesignFilename))
|
||||
{
|
||||
var customAvatarItem = new CustomAvatarItem
|
||||
{
|
||||
Id = recNetResult.CustomAvatarItemId,
|
||||
Accessibility=recNetResult.Accessibility,
|
||||
Creator = importAccount,
|
||||
Name = recNetResult.Name,
|
||||
Description = recNetResult.Description,
|
||||
Price = recNetResult.Price,
|
||||
BaseAvatarItemId = recNetResult.BaseAvatarItemId,
|
||||
BaseAvatarItemColor = ColorTranslator.FromHtml(recNetResult.BaseAvatarItemColor),
|
||||
DesignFilename = recNetResult.DesignFilename,
|
||||
ThumbnailImageFilename = recNetResult.ThumbnailImageFilename,
|
||||
PreviewOrientation = recNetResult.PreviewOrientation
|
||||
};
|
||||
|
||||
db.CustomAvatarItems.Insert(customAvatarItem);
|
||||
return customAvatarItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DTOs
|
||||
|
||||
public class CustomAvatarItemMetaDTO
|
||||
{
|
||||
[Required] public required string Name { get; set; }
|
||||
[Required] public required string Description { get; set; }
|
||||
[Required] public required int Price { get; set; }
|
||||
[Required] public required long BaseAvatarItemId { get; set; }
|
||||
[Required] public required string BaseAvatarItemColor { get; set; }
|
||||
[Required] public required RoomAccessibility Accessibility { get; set; }
|
||||
[Required] public required PreviewOrientationType PreviewOrientation { get; set; }
|
||||
}
|
||||
|
||||
public class CAvatarItemDto
|
||||
{
|
||||
public Guid CustomAvatarItemId { get; set; }
|
||||
public long CreatorAccountId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public int Price { get; set; }
|
||||
public RoomAccessibility Accessibility { get; set; }
|
||||
public bool IsFeatured { get; set; }
|
||||
public string BaseAvatarItemColor { get; set; } = string.Empty;
|
||||
public string DesignFilename { get; set; } = string.Empty;
|
||||
public string ThumbnailImageFilename { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ModifiedAt { get; set; }
|
||||
public int? BaseAvatarItemId { get; set; }
|
||||
public PreviewOrientationType PreviewOrientation { get; set; } = PreviewOrientationType.Front;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user