using System.ComponentModel.DataAnnotations; using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Sticker; [ApiController] [Route("/stickers")] public class StickerController(AppDatabase db, StickerService st) : ControllerBase { private async Task _CheckStickerPackPermissions(Guid packId, Account.Account currentUser, PublisherMemberRole requiredRole) { var pack = await db.StickerPacks .Include(p => p.Publisher) .FirstOrDefaultAsync(p => p.Id == packId); if (pack is null) return NotFound("Sticker pack not found"); var member = await db.PublisherMembers .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < requiredRole) return StatusCode(403, $"You need to be at least a {requiredRole} to perform this action"); return Ok(); } [HttpGet] public async Task>> ListStickerPacks([FromQuery] int offset = 0, [FromQuery] int take = 20) { var totalCount = await db.StickerPacks.CountAsync(); var packs = await db.StickerPacks .OrderByDescending(e => e.CreatedAt) .Skip(offset) .Take(take) .ToListAsync(); Response.Headers["X-Total"] = totalCount.ToString(); return Ok(packs); } [HttpGet("{id:guid}")] public async Task> GetStickerPack(Guid id) { var pack = await db.StickerPacks .FirstOrDefaultAsync(p => p.Id == id); if (pack is null) return NotFound(); return Ok(pack); } public class StickerPackRequest { [MaxLength(1024)] public string? Name { get; set; } [MaxLength(4096)] public string? Description { get; set; } [MaxLength(128)] public string? Prefix { get; set; } } [HttpPost] [RequiredPermission("global", "stickers.packs.create")] public async Task> CreateStickerPack([FromBody] StickerPackRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (string.IsNullOrEmpty(request.Name)) return BadRequest("Name is required"); if (string.IsNullOrEmpty(request.Prefix)) return BadRequest("Prefix is required"); var publisherName = Request.Headers["X-Pub"].ToString(); if (string.IsNullOrEmpty(publisherName)) return BadRequest("Publisher name is required in X-Pub header"); var publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Name == publisherName && p.AccountId == currentUser.Id); if (publisher == null) return BadRequest("Publisher not found"); var pack = new StickerPack { Name = request.Name!, Description = request.Description ?? string.Empty, Prefix = request.Prefix!, PublisherId = publisher.Id }; db.StickerPacks.Add(pack); await db.SaveChangesAsync(); return Ok(pack); } [HttpPatch("{id:guid}")] public async Task> UpdateStickerPack(Guid id, [FromBody] StickerPackRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var pack = await db.StickerPacks .Include(p => p.Publisher) .FirstOrDefaultAsync(p => p.Id == id); if (pack is null) return NotFound(); var member = await db.PublisherMembers .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < PublisherMemberRole.Editor) return StatusCode(403, "You need to be at least an editor to update sticker packs"); if (request.Name is not null) pack.Name = request.Name; if (request.Description is not null) pack.Description = request.Description; if (request.Prefix is not null) pack.Prefix = request.Prefix; db.StickerPacks.Update(pack); await db.SaveChangesAsync(); return Ok(pack); } [HttpDelete("{id:guid}")] public async Task DeleteStickerPack(Guid id) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var pack = await db.StickerPacks .Include(p => p.Publisher) .FirstOrDefaultAsync(p => p.Id == id); if (pack is null) return NotFound(); var member = await db.PublisherMembers .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); if (member is null) return StatusCode(403, "You are not a member of this publisher"); if (member.Role < PublisherMemberRole.Editor) return StatusCode(403, "You need to be an editor to delete sticker packs"); await st.DeleteStickerPackAsync(pack); return NoContent(); } [HttpGet("{packId:guid}/content")] public async Task>> ListStickers(Guid packId) { var stickers = await db.Stickers .Where(s => s.Pack.Id == packId) .Include(e => e.Pack) .Include(e => e.Image) .OrderByDescending(e => e.CreatedAt) .ToListAsync(); return Ok(stickers); } [HttpGet("lookup/{identifier}")] public async Task> GetStickerByIdentifier(string identifier) { IQueryable query = db.Stickers .Include(e => e.Pack) .Include(e => e.Image); query = Guid.TryParse(identifier, out var guid) ? query.Where(e => e.Id == guid) : query.Where(e => e.Pack.Prefix + e.Slug == identifier); var sticker = await query.FirstOrDefaultAsync(); if (sticker is null) return NotFound(); return Ok(sticker); } [HttpGet("{packId:guid}/content/{id:guid}")] public async Task> GetSticker(Guid packId, Guid id) { var sticker = await db.Stickers .Where(s => s.Pack.Id == packId && s.Id == id) .Include(e => e.Pack) .Include(e => e.Image) .FirstOrDefaultAsync(); if (sticker is null) return NotFound(); return Ok(sticker); } public class StickerRequest { [MaxLength(128)] public string? Slug { get; set; } = null!; public string? ImageId { get; set; } } [HttpPatch("{packId:guid}/content/{id:guid}")] public async Task UpdateSticker(Guid packId, Guid id, [FromBody] StickerRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; var sticker = await db.Stickers .Include(s => s.Image) .Include(s => s.Pack) .ThenInclude(p => p.Publisher) .FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId); if (sticker is null) return NotFound(); if (request.Slug is not null) sticker.Slug = request.Slug; CloudFile? image = null; if (request.ImageId is not null) { image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId); if (image is null) return BadRequest("Image not found"); sticker.ImageId = request.ImageId; } sticker = await st.UpdateStickerAsync(sticker, image); return Ok(sticker); } [HttpDelete("{packId:guid}/content/{id:guid}")] public async Task DeleteSticker(Guid packId, Guid id) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; var sticker = await db.Stickers .Include(s => s.Image) .Include(s => s.Pack) .ThenInclude(p => p.Publisher) .FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId); if (sticker is null) return NotFound(); await st.DeleteStickerAsync(sticker); return NoContent(); } public const int MaxStickersPerPack = 24; [HttpPost("{packId:guid}/content")] [RequiredPermission("global", "stickers.create")] public async Task CreateSticker(Guid packId, [FromBody] StickerRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (string.IsNullOrWhiteSpace(request.Slug)) return BadRequest("Slug is required."); if (request.ImageId is null) return BadRequest("Image is required."); var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor); if (permissionCheck is not OkResult) return permissionCheck; var pack = await db.StickerPacks .Include(p => p.Publisher) .FirstOrDefaultAsync(e => e.Id == packId); if (pack is null) return BadRequest("Sticker pack was not found."); var stickersCount = await db.Stickers.CountAsync(s => s.PackId == packId); if (stickersCount >= MaxStickersPerPack) return BadRequest($"Sticker pack has reached maximum capacity of {MaxStickersPerPack} stickers."); var image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId); if (image is null) return BadRequest("Image was not found."); var sticker = new Sticker { Slug = request.Slug, ImageId = image.Id, Image = image, Pack = pack }; sticker = await st.CreateStickerAsync(sticker); return Ok(sticker); } }