using System.Security.Claims; using System.Text.Json; using FastEndpoints; using FluentValidation; using Microsoft.EntityFrameworkCore; using TrackQrApi.Data; using TrackQrApi.Features.Auth.Common; using TrackQrApi.Features.Plans.Services; using TrackQrApi.Features.QRCodes.Common; using TrackQrApi.Models; namespace TrackQrApi.Features.QRCodes.Endpoints; public class CreateQRCodeRequest { public Guid WorkspaceId { get; set; } public Guid? ProjectId { get; set; } public Guid? ShortLinkId { get; set; } public Guid? LogoAssetId { get; set; } public string? Name { get; set; } public QRCodeStyle? Style { get; set; } } public class CreateQRCodeValidator : Validator { public CreateQRCodeValidator() { RuleFor(x => x.ShortLinkId) .NotEmpty().WithMessage("ShortLinkId is required"); } } public class CreateQRCodeEndpoint(AppDbContext db, IPlanLimitsService planLimits) : Endpoint { public override void Configure() { Post("/workspaces/{WorkspaceId}/qrcodes"); } public override async Task HandleAsync(CreateQRCodeRequest req, CancellationToken ct) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); // Verify workspace ownership var workspaceExists = await db.Workspaces .AnyAsync(w => w.Id == req.WorkspaceId && w.OwnerUserId == userId, ct); if (!workspaceExists) { await HttpContext.Response.SendAsync(new MessageResponse("Workspace not found"), 404, cancellation: ct); return; } // Check plan limits if (!await planLimits.CanCreateQRCodeAsync(req.WorkspaceId, ct)) { await HttpContext.Response.SendAsync( new MessageResponse("QR code limit reached. Please upgrade your plan to create more QR codes."), 402, cancellation: ct); return; } // Verify short link belongs to workspace string? linkSlug = null; if (req.ShortLinkId.HasValue) { var link = await db.ShortLinks .Where(l => l.Id == req.ShortLinkId.Value && l.WorkspaceId == req.WorkspaceId) .Select(l => new { l.Slug }) .FirstOrDefaultAsync(ct); if (link is null) { await HttpContext.Response.SendAsync(new MessageResponse("Short link not found"), 404, cancellation: ct); return; } linkSlug = link.Slug; } // Verify project belongs to workspace if specified if (req.ProjectId.HasValue) { var projectExists = await db.Projects .AnyAsync(p => p.Id == req.ProjectId.Value && p.WorkspaceId == req.WorkspaceId, ct); if (!projectExists) { await HttpContext.Response.SendAsync(new MessageResponse("Project not found"), 404, cancellation: ct); return; } } // Verify logo asset belongs to workspace if specified string? logoUrl = null; if (req.LogoAssetId.HasValue) { var asset = await db.Assets .Where(a => a.Id == req.LogoAssetId.Value && a.WorkspaceId == req.WorkspaceId) .Select(a => new { a.StorageKey }) .FirstOrDefaultAsync(ct); if (asset is null) { await HttpContext.Response.SendAsync(new MessageResponse("Logo asset not found"), 404, cancellation: ct); return; } logoUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/assets/{asset.StorageKey}"; } var style = req.Style ?? new QRCodeStyle(); var name = req.Name ?? $"QR Code {DateTime.UtcNow:yyyy-MM-dd}"; var now = DateTime.UtcNow; var qrCode = new QRCodeDesign { Id = Guid.NewGuid(), WorkspaceId = req.WorkspaceId, ProjectId = req.ProjectId, ShortLinkId = req.ShortLinkId, Name = name, StyleJson = JsonSerializer.Serialize(style), LogoAssetId = req.LogoAssetId, CreatedAt = now, UpdatedAt = now }; db.QrCodeDesigns.Add(qrCode); await db.SaveChangesAsync(ct); var response = new QRCodeResponse( qrCode.Id, qrCode.WorkspaceId, qrCode.ProjectId, qrCode.ShortLinkId, linkSlug, qrCode.Name, style, qrCode.LogoAssetId, logoUrl, qrCode.CreatedAt, qrCode.UpdatedAt ); await HttpContext.Response.SendAsync(response, 201, cancellation: ct); } }