Premier commit
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using Azure.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Graph;
|
||||
using SIFinance.Shared;
|
||||
using SIFinance.Shared.Entities;
|
||||
|
||||
namespace SIFinance.Api;
|
||||
|
||||
public record SharePointSiteDto(string Id, string Name, string? WebUrl);
|
||||
|
||||
public record SharePointUploadDto(string WebUrl, string ItemId, long Size, string Nom);
|
||||
|
||||
// Endpoints de découverte SharePoint + upload de documents dans les dossiers modules
|
||||
// (Achats / Sous-Traitants / Contrat Etudiant) du drive configuré (SharePoint:DriveId).
|
||||
public static class SharePointEndpoints
|
||||
{
|
||||
// Whitelist stricte plutôt que d'accepter n'importe quel nom de dossier envoyé par le
|
||||
// client — évite qu'un chemin arbitraire soit construit dans l'URL Graph.
|
||||
private static readonly HashSet<string> DossiersAutorises =
|
||||
new(StringComparer.OrdinalIgnoreCase) { "Achats", "Sous-Traitants", "Contrat Etudiant" };
|
||||
|
||||
public static IEndpointRouteBuilder MapSharePointEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/sharepoint").WithTags("SharePoint").RequireAuthorization();
|
||||
|
||||
group.MapGet("/sites", async (SharePointGraphClientFactory graphFactory, CancellationToken ct) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var graph = graphFactory.Create();
|
||||
var result = await graph.Sites.GetAsync(cfg => cfg.QueryParameters.Search = "*", ct);
|
||||
var sites = (result?.Value ?? [])
|
||||
.Select(s => new SharePointSiteDto(s.Id ?? "", s.Name ?? s.DisplayName ?? "(sans nom)", s.WebUrl))
|
||||
.ToList();
|
||||
return Results.Ok(sites);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Cas fréquents : secret pas encore renseigné, permission Sites.Read.All
|
||||
// absente, ou admin consent pas encore accordé (403).
|
||||
return Results.BadRequest(new
|
||||
{
|
||||
error = "Impossible de récupérer la liste des sites SharePoint — vérifie les identifiants et les permissions (Sites.Read.All ou Sites.ReadWrite.All, avec admin consent).",
|
||||
detail = ex.Message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
group.MapPost("/upload", async (
|
||||
HttpRequest request, SharePointUploadService uploadService,
|
||||
SIFinanceDbContext db, CurrentUserService currentUser, CancellationToken ct) =>
|
||||
{
|
||||
if (!request.HasFormContentType)
|
||||
return Results.BadRequest(new { error = "Contenu multipart attendu." });
|
||||
|
||||
var form = await request.ReadFormAsync(ct);
|
||||
var file = form.Files.GetFile("file");
|
||||
var dossier = form["dossier"].ToString();
|
||||
var service = form["service"].ToString();
|
||||
|
||||
if (file is null || file.Length == 0)
|
||||
return Results.BadRequest(new { error = "Fichier manquant." });
|
||||
if (!DossiersAutorises.Contains(dossier))
|
||||
return Results.BadRequest(new { error = "Dossier de destination invalide." });
|
||||
|
||||
// Le sous-dossier "collaborateur" est résolu depuis l'utilisateur authentifié,
|
||||
// jamais depuis une valeur envoyée par le client — sinon n'importe qui pourrait
|
||||
// faire apparaître son upload dans le dossier d'un collègue.
|
||||
var userId = await currentUser.GetOrCreateCurrentUserIdAsync(ct);
|
||||
var user = await db.Utilisateurs.AsNoTracking().FirstOrDefaultAsync(u => u.Id == userId, ct);
|
||||
var collaborateur = user is null ? "Autre" : $"{user.Prenom} {user.Nom}";
|
||||
|
||||
var segments = new List<string> { dossier };
|
||||
if (!string.IsNullOrWhiteSpace(service)) segments.Add(SanitizeSegment(service));
|
||||
segments.Add(SanitizeSegment(collaborateur));
|
||||
var cheminDossier = string.Join('/', segments);
|
||||
|
||||
try
|
||||
{
|
||||
await using var stream = file.OpenReadStream();
|
||||
var result = await uploadService.UploadAsync(cheminDossier, file.FileName, stream, file.ContentType, ct);
|
||||
return Results.Ok(new SharePointUploadDto(result.WebUrl, result.ItemId, result.Size, file.FileName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "Échec de l'upload vers SharePoint.", detail = ex.Message });
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static string SanitizeSegment(string value)
|
||||
{
|
||||
var trimmed = value.Trim();
|
||||
if (trimmed.Length == 0) return "Autre";
|
||||
var invalides = new[] { '"', '*', ':', '<', '>', '?', '/', '\\', '|' };
|
||||
var nettoye = new string(trimmed.Select(c => invalides.Contains(c) ? '-' : c).ToArray());
|
||||
return nettoye.Length == 0 ? "Autre" : nettoye;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user