using Microsoft.AspNetCore.Mvc; using Vanadium.Auth; using Vanadium; using Vanadium.Classes.DBs; using Vanadium.Classes.DBs.DBClasses; using System.ComponentModel.DataAnnotations; using System.Text.Json; using static Vanadium.Classes.DBs.DBClasses.CustomClothingDBClasses; namespace Vanadium.Controllers /* experimental */ { [ApiController] [Route("api/customAvatarItems")] public class CustomClothingController : ControllerBase { [HttpGet("v1/isCreationEnabled")] [HttpGet("v1/isRenderingEnabled")] public IActionResult IsCreationEnabled() => Ok(true); [HttpGet("v1/minPriceForPublicItem")] public IActionResult MinPriceForPublicItem() => Ok(0); [HttpGet("v1/featured")] public IActionResult FeaturedClothing() { var items = CustomClothingDB.GetFeatured(20) .Select(x => x.ToDictionary()) .ToList(); return Ok(items); } [HttpGet("v1/hot")] public IActionResult HotClothing() { var items = CustomClothingDB.GetHot(20) .Select(x => x.ToDictionary()) .ToList(); return Ok(items); } [HttpGet("v1/isCreationAllowedForAccount")] public IActionResult IsCreationAllowedForAccount() { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); var player = PlayerDB.Players.FindById(playerId.Value); if (player == null) return Unauthorized(""); bool allowed = player.PlayerRoles.Contains(PlayerDBClasses.PlayerRoles.Developer); return Ok(new { Success = allowed }); } [HttpGet("/econ/customAvatarItems/v1/owned")] public IActionResult Owned([FromQuery] int skip = 0, [FromQuery] int take = 100) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); int total = CustomClothingDB.CountByCreator(playerId.Value, true); var items = CustomClothingDB.GetByCreator(playerId.Value, true) .Skip(skip) .Take(take) .Select(x => x.ToDictionary()); return Ok(new { Results = items, TotalResults = total }); } [HttpGet("v2/fromCreator/{accId}")] public IActionResult FromCreator(long accId, [FromQuery] int skip = 0, [FromQuery] int take = 100) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); bool isCreator = playerId.Value == accId; int total = CustomClothingDB.CountByCreator(accId, isCreator); var items = CustomClothingDB.GetByCreator(accId, isCreator) .Skip(skip) .Take(take) .Select(x => x.ToDictionary()); return Ok(new { Results = items, TotalResults = total }); } [HttpPost("v1")] public async Task Create( [FromForm(Name = "thumbnailImage"), Required] IFormFile thumbnailImage, [FromForm(Name = "design"), Required] IFormFile design, [FromForm(Name = "metadata"), Required] string metaJson) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); var player = PlayerDB.Players.FindById(playerId.Value); if (player == null) return Unauthorized(""); bool canCreate = player.PlayerRoles.Contains(PlayerDBClasses.PlayerRoles.Developer); if (!canCreate) return StatusCode(StatusCodes.Status403Forbidden); CustomClothingMetaRequest? meta; try { meta = JsonSerializer.Deserialize(metaJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (meta == null) return BadRequest("Invalid metadata format."); } catch (Exception ex) { return BadRequest($"Metadata is not valid JSON. {ex}"); } string? thumbFilename = await SaveUploadedFileAsync(thumbnailImage, (long)playerId); if (thumbFilename == null) return StatusCode(StatusCodes.Status500InternalServerError, "Failed to save thumbnail image."); string? designFilename = await SaveUploadedFileAsync(design, (long)playerId); if (designFilename == null) return StatusCode(StatusCodes.Status500InternalServerError, "Failed to save design image."); var item = new CustomClothingItem { CreatorAccountId = playerId.Value, Name = meta.Name, Description = meta.Description, Price = meta.Price, BaseAvatarItemId = meta.BaseAvatarItemId, BaseAvatarItemColor = meta.BaseAvatarItemColor, Accessibility = meta.Accessibility, ThumbnailImageFilename = thumbFilename, DesignFilename = designFilename }; CustomClothingDB.Insert(item); return Ok(new { Success = true, Value = item.ToDictionary() }); } [HttpPut("v1/design")] public async Task UploadDesign( [FromForm(Name = "customAvatarItemId"), Required] Guid customAvatarItemId, [FromForm(Name = "design"), Required] IFormFile designImage) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); var item = CustomClothingDB.FindById(customAvatarItemId); if (item == null) return NotFound("Clothing item not found."); if (item.CreatorAccountId != playerId.Value) return StatusCode(StatusCodes.Status403Forbidden, "You do not own this item."); string? designFilename = await SaveUploadedFileAsync(designImage, (long)playerId); if (designFilename == null) return StatusCode(StatusCodes.Status500InternalServerError, "Failed to save design image."); item.DesignFilename = designFilename; CustomClothingDB.Update(item); return Ok(new { Success = true, Value = item.ToDictionary() }); } [HttpPost("v1/bulk")] public IActionResult Bulk([FromForm(Name = "customAvatarItemIds")] List customAvatarItemIds) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); var items = CustomClothingDB.GetBulk(customAvatarItemIds); return Ok(items.Select(x => x.ToDictionary()).ToList()); } [HttpGet("v1/{customAvatarItemId}")] public IActionResult Get(Guid customAvatarItemId) { var playerId = AuthStuff.GetPlayerId(Request); if (playerId == null) return Unauthorized(""); var item = CustomClothingDB.FindById(customAvatarItemId); if (item == null) return NotFound(""); return Ok(item.ToDictionary()); } private static async Task SaveUploadedFileAsync(IFormFile? file, long id) { if (file == null || file.Length == 0) { Console.WriteLine("Upload attempted with an empty or null file."); return null; } try { if (!file.ContentType.StartsWith("image/")) { Console.WriteLine("Rejected file upload: Not an image. Content-Type: {ContentType}", file.ContentType); return null; } string dir = Path.Combine(Program.dataDir, "Images", "player_shirts", id.ToString()); Directory.CreateDirectory(dir); string filename = $"{Guid.NewGuid()}_customshirt.png"; string fullPath = Path.Combine(dir, filename); using var stream = new FileStream( fullPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true); await file.CopyToAsync(stream).ConfigureAwait(false); return filename; } catch (Exception ex) { Console.WriteLine($"An error occurred while saving the uploaded file. {ex.Message}"); return null; } } } }