Fase 3: Soporte de Imágenes en Wizard y Backend (Upload Local)
This commit is contained in:
56
src/SIGCM.API/Controllers/ImagesController.cs
Normal file
56
src/SIGCM.API/Controllers/ImagesController.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ if (app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles(); // Enable static files for images
|
||||
|
||||
app.UseCors("AllowFrontend");
|
||||
|
||||
|
||||
10
src/SIGCM.Domain/Entities/ListingImage.cs
Normal file
10
src/SIGCM.Domain/Entities/ListingImage.cs
Normal 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; }
|
||||
}
|
||||
9
src/SIGCM.Domain/Interfaces/IImageRepository.cs
Normal file
9
src/SIGCM.Domain/Interfaces/IImageRepository.cs
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
33
src/SIGCM.Infrastructure/Repositories/ImageRepository.cs
Normal file
33
src/SIGCM.Infrastructure/Repositories/ImageRepository.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user