Fase 3: Soporte de Imágenes en Wizard y Backend (Upload Local)

This commit is contained in:
2025-12-17 13:56:47 -03:00
parent 1b88394b00
commit 8f535f3a6e
14 changed files with 311 additions and 3 deletions

View File

@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Mvc;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
namespace SIGCM.API.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ImagesController : ControllerBase
{
private readonly IImageRepository _repository;
private readonly IWebHostEnvironment _env;
public ImagesController(IImageRepository repository, IWebHostEnvironment env)
{
_repository = repository;
_env = env;
}
[HttpPost("upload/{listingId}")]
public async Task<IActionResult> Upload(int listingId, IFormFile file)
{
if (file == null || file.Length == 0) return BadRequest("File is empty");
// Basic validation
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(ext)) return BadRequest("Invalid file type");
// Ensure directory exists
var uploadDir = Path.Combine(_env.WebRootPath, "uploads", "listings", listingId.ToString());
Directory.CreateDirectory(uploadDir);
// Save file
var fileName = $"{Guid.NewGuid()}{ext}";
var filePath = Path.Combine(uploadDir, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Save metadata
var relativeUrl = $"/uploads/listings/{listingId}/{fileName}";
var image = new ListingImage
{
ListingId = listingId,
Url = relativeUrl,
IsMainInfo = false, // Logic to set first as main could be added here
DisplayOrder = 0
};
await _repository.AddAsync(image);
return Ok(new { Url = relativeUrl });
}
}

View File

@@ -31,6 +31,7 @@ if (app.Environment.IsDevelopment())
}
app.UseHttpsRedirection();
app.UseStaticFiles(); // Enable static files for images
app.UseCors("AllowFrontend");

View File

@@ -0,0 +1,10 @@
namespace SIGCM.Domain.Entities;
public class ListingImage
{
public int Id { get; set; }
public int ListingId { get; set; }
public required string Url { get; set; }
public bool IsMainInfo { get; set; }
public int DisplayOrder { get; set; }
}

View File

@@ -0,0 +1,9 @@
using SIGCM.Domain.Entities;
namespace SIGCM.Domain.Interfaces;
public interface IImageRepository
{
Task AddAsync(ListingImage image);
Task<IEnumerable<ListingImage>> GetByListingIdAsync(int listingId);
}

View File

@@ -154,6 +154,18 @@ BEGIN
FOREIGN KEY (AttributeDefinitionId) REFERENCES AttributeDefinitions(Id) ON DELETE NO ACTION
);
END
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'ListingImages')
BEGIN
CREATE TABLE ListingImages (
Id INT IDENTITY(1,1) PRIMARY KEY,
ListingId INT NOT NULL,
Url NVARCHAR(500) NOT NULL,
IsMainInfo BIT DEFAULT 0,
DisplayOrder INT DEFAULT 0,
FOREIGN KEY (ListingId) REFERENCES Listings(Id) ON DELETE CASCADE
);
END
";
await connection.ExecuteAsync(schemaSql);
}

View File

@@ -19,6 +19,7 @@ public static class DependencyInjection
services.AddScoped<IAuthService, Services.AuthService>();
services.AddScoped<IAttributeDefinitionRepository, AttributeDefinitionRepository>();
services.AddScoped<IListingRepository, ListingRepository>();
services.AddScoped<IImageRepository, ImageRepository>();
return services;
}
}

View File

@@ -0,0 +1,33 @@
using Dapper;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
using SIGCM.Infrastructure.Data;
namespace SIGCM.Infrastructure.Repositories;
public class ImageRepository : IImageRepository
{
private readonly IDbConnectionFactory _connectionFactory;
public ImageRepository(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task AddAsync(ListingImage image)
{
using var conn = _connectionFactory.CreateConnection();
var sql = @"
INSERT INTO ListingImages (ListingId, Url, IsMainInfo, DisplayOrder)
VALUES (@ListingId, @Url, @IsMainInfo, @DisplayOrder)";
await conn.ExecuteAsync(sql, image);
}
public async Task<IEnumerable<ListingImage>> GetByListingIdAsync(int listingId)
{
using var conn = _connectionFactory.CreateConnection();
return await conn.QueryAsync<ListingImage>(
"SELECT * FROM ListingImages WHERE ListingId = @ListingId ORDER BY DisplayOrder",
new { ListingId = listingId });
}
}