CreatePost - Upload files, thumbnail, external link

This commit is contained in:
PascalMarchesseault
2024-11-24 20:18:58 -05:00
parent ebf0c0da96
commit cd827588a1

View File

@@ -4,6 +4,7 @@ using Hutopy.Web.Common.BlobStorage;
using Hutopy.Web.Common.Security;
using Hutopy.Web.Features.Contents.Data;
using Hutopy.Web.Features.Contents.Handlers.Models;
using Microsoft.EntityFrameworkCore;
namespace Hutopy.Web.Features.Contents.Handlers;
@@ -14,7 +15,7 @@ public record PostContentRequest(
string Title,
string Description,
IFormFileCollection? Files,
IFormFile? Thumbnail, // Nouveau champ pour le thumbnail
IFormFile? Thumbnail,
string[]? ExternalUrls);
[PublicAPI]
@@ -39,11 +40,15 @@ public sealed class PostContentRequestValidator : Validator<PostContentRequest>
.NotEmpty().WithMessage("You should specify a valid/not empty Description");
RuleForEach(r => r.ExternalUrls)
.NotEmpty().WithMessage("External URL cannot be empty")
.Must(url => Uri.IsWellFormedUriString(url, UriKind.Absolute)).WithMessage("External URL is not valid");
.Must(url => Uri.IsWellFormedUriString(url, UriKind.Absolute) &&
(url.StartsWith("http://") || url.StartsWith("https://")))
.WithMessage("External URL must be a valid HTTP/HTTPS URL");
RuleFor(r => r.Thumbnail)
.Must(file => file == null || file.Length > 0).WithMessage("Thumbnail file is invalid.");
.Must(file => file == null || file.ContentType.StartsWith("image/"))
.WithMessage("Thumbnail must be an image");
}
}
@@ -59,20 +64,19 @@ public sealed class PostContent(
AllowFileUploads();
}
public override async Task HandleAsync(
PostContentRequest req,
CancellationToken ct)
public override async Task HandleAsync(PostContentRequest req, CancellationToken ct)
{
var urls = new ConcurrentBag<string>();
string? thumbnailUrl = null;
// Traitement des fichiers uploadés
if (req.Files is not null)
using var transaction = await context.Database.BeginTransactionAsync(ct);
try
{
await Parallel.ForEachAsync(
req.Files,
ct,
async (file, ict) =>
if (req.Files is not null)
{
await Parallel.ForEachAsync(req.Files, ct, async (file, ict) =>
{
try
{
@@ -81,32 +85,35 @@ public sealed class PostContent(
}
catch (Exception ex)
{
Logger.LogError("{ErrorMessage}", ex.Message);
Logger.LogError("Failed to upload file {FileName}: {Message}", file.FileName, ex.Message);
}
});
}
// Téléversement du thumbnail
if (req.Thumbnail is not null)
{
try
{
thumbnailUrl = await SaveFileAsync(
req.CreatorId,
req.Id,
req.Thumbnail,
ct,
isThumbnail: true); // Utilisation d'un chemin spécifique pour le thumbnail
}
catch (Exception ex)
{
Logger.LogError("Error uploading thumbnail: {ErrorMessage}", ex.Message);
}
}
// Ajout à la base de données
await context.Contents.AddAsync(
new Content
if (req.ExternalUrls is not null)
{
foreach (var externalUrl in req.ExternalUrls.Where(url => !string.IsNullOrWhiteSpace(url)))
{
urls.Add(externalUrl);
}
}
if (req.Thumbnail is not null)
{
try
{
thumbnailUrl = await SaveFileAsync(req.CreatorId, req.Id, req.Thumbnail, ct, isThumbnail: true);
}
catch (Exception ex)
{
Logger.LogError("Error uploading thumbnail: {Message}", ex.Message);
}
}
await context.Contents.AddAsync(new Content
{
Id = req.Id,
CreatedBy = User.GetUserId(),
@@ -114,33 +121,19 @@ public sealed class PostContent(
Description = req.Description,
Urls = urls.IsEmpty ? null : urls.ToArray(),
ThumbnailUrl = thumbnailUrl,
},
ct);
}, ct);
await context.SaveChangesAsync(ct);
await context.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
// Récupérer le contenu pour le retour
var content = await context
.Contents
.Select(c => new ContentModel
{
Id = c.Id,
CreatedBy = c.CreatedBy,
CreatedByName = c.Creator!.Name,
CreatedByPortraitUrl = c.Creator.Images.Logo,
CreatedAt = c.CreatedAt,
DeletedBy = c.DeletedBy,
DeletedAt = c.DeletedAt,
Title = c.Title,
Description = c.Description,
Urls = c.Urls,
ThumbnailUrl = c.ThumbnailUrl,
})
.SingleOrDefaultAsync(
c => c.Id == req.Id,
cancellationToken: ct);
await SendOkAsync(content, ct);
await SendOkAsync(new { Message = "Content published successfully!" }, ct);
}
catch (Exception ex)
{
await transaction.RollbackAsync(ct);
Logger.LogError("Transaction failed: {Message}", ex.Message);
throw;
}
}
private async Task<string> SaveFileAsync(
@@ -148,21 +141,19 @@ public sealed class PostContent(
Guid contentId,
IFormFile file,
CancellationToken ct = default,
bool isThumbnail = false) // Nouveau paramètre pour indiquer si c'est un thumbnail
bool isThumbnail = false)
{
// Détermine le chemin du blob
var blobName = isThumbnail
? $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/thumbnail-{file.FileName}" // Chemin pour le thumbnail
: $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}"; // Chemin pour les fichiers normaux
// Téléverse le fichier
var url = await blobStorage.UploadFileAsync(
var blobName = isThumbnail
? $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/thumbnail-{file.FileName}"
: $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}";
return await blobStorage.UploadFileAsync(
ContainerNames.Creators,
blobName,
file.OpenReadStream(),
file.ContentType,
ct: ct);
return url;
}
}